5

ファイルのコレクションをダウンロードするための FTP Web 要求のコレクションを作成しようとしています。

これを単一のスレッドで正しく実行していましたが、現在複数のスレッドで実行しようとしていますが、タイムアウト例外が発生しています。かなり単純なものが欠けていると思いますが、うまくいかないようです

コードは次のとおりです。

internal static void DownloadLogFiles(IEnumerable<string> ftpFileNames, string localLogsFolder)
{
    BotFinder.DeleteAllFilesFromDirectory(localLogsFolder);

    var ftpWebRequests = new Collection<FtpWebRequest>();

    // Create web request for each log filename
    foreach (var ftpWebRequest in ftpFileNames.Select(filename => (FtpWebRequest) WebRequest.Create(filename)))
    {
        ftpWebRequest.Credentials = new NetworkCredential(BotFinderSettings.FtpUserId, BotFinderSettings.FtpPassword);
        ftpWebRequest.KeepAlive = false;
        ftpWebRequest.UseBinary = true;
        ftpWebRequest.CachePolicy = NoCachePolicy;
        ftpWebRequest.Method = WebRequestMethods.Ftp.DownloadFile;
        ftpWebRequests.Add(ftpWebRequest);
    }

    var threadDoneEvents = new ManualResetEvent[ftpWebRequests.Count];

    for (var x = 0; x < ftpWebRequests.Count; x++)
    {
        var ftpWebRequest = ftpWebRequests[x];
        threadDoneEvents[x] = new ManualResetEvent(false);
        var threadedFtpDownloader = new ThreadedFtpDownloader(ftpWebRequest, threadDoneEvents[x]);
        ThreadPool.QueueUserWorkItem(threadedFtpDownloader.PerformFtpRequest, localLogsFolder);               
    }

    WaitHandle.WaitAll(threadDoneEvents);
}

class ThreadedFtpDownloader
{
    private ManualResetEvent threadDoneEvent;
    private readonly FtpWebRequest ftpWebRequest;

    /// <summary>
    /// 
    /// </summary>
    public ThreadedFtpDownloader(FtpWebRequest ftpWebRequest, ManualResetEvent threadDoneEvent)
    {
        this.threadDoneEvent = threadDoneEvent;
        this.ftpWebRequest = ftpWebRequest;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="localLogsFolder">
    /// 
    /// </param>
    internal void PerformFtpRequest(object localLogsFolder)
    {
        try
        {
            // TIMEOUT IS HAPPENING ON LINE BELOW
            using (var response = ftpWebRequest.GetResponse())
            {
                using (var responseStream = response.GetResponseStream())
                {
                    const int length = 1024*10;
                    var buffer = new Byte[length];
                    var bytesRead = responseStream.Read(buffer, 0, length);

                    var logFileToCreate = string.Format("{0}{1}{2}", localLogsFolder,
                                        ftpWebRequest.RequestUri.Segments[3].Replace("/", "-"),
                                        ftpWebRequest.RequestUri.Segments[4]);

                    using (var writeStream = new FileStream(logFileToCreate, FileMode.OpenOrCreate))
                    {
                        while (bytesRead > 0)
                        {
                            writeStream.Write(buffer, 0, bytesRead);
                            bytesRead = responseStream.Read(buffer, 0, length);
                        }
                    }
                }
            }

            threadDoneEvent.Set();
        }
        catch (Exception exception)
        {
            BotFinder.HandleExceptionAndExit(exception);
        }
    }
}

最初の 2 つのファイルをダウンロードしているようです (2 つのスレッドを使用すると想定しています) が、これらが完了してアプリケーションが次のファイルに移動しようとするとタイムアウトが発生するようです。

タイムアウトしている FTPWebRequest が有効でファイルが存在することを確認できたので、接続が開いているか何かではないかと思います。


コメントを投稿するつもりでしたが、おそらく回答の方が読みやすいでしょう:

まず、ftpRequest.Timout プロパティを Timeout.Infinite に設定すると、タイムアウトの問題はなくなりますが、タイムアウトを無限にすることはおそらくベスト プラクティスではありません。だから私はこれを別の方法で解決したいと思います...

コードをデバッグすると、次のようになることがわかります。

ThreadPool.QueueUserWorkItem(threadedFtpDownloader.PerformFtpRequest, localLogsFolder);

FTP Web リクエストごとに PerformFtpRequest メソッドに入り、ftpWebRequest.GetResponse() を呼び出しますが、その後は最初の 2 つのリクエストに対してのみ先に進みます。残りのリクエストはアクティブなままですが、最初の 2 つが終了するまで先に進みません。したがって、これは基本的に、開始前に他のリクエストが完了するのを待っている間、それらが開いたままになることを意味します。

この問題の解決策は、すべてのリクエストを一度に実行できるようにするか (ConnectionLimit プロパティはここでは効果がありません)、実際にレスポンスを使用する準備が整うまで実行が GetResponse を呼び出さないようにすることだと思います。

これを解決する最善の方法について何か良いアイデアはありますか? 現時点では、私が考えているように見えるのは、避けたいハッキーなソリューションだけです:)

ありがとう!

4

1 に答える 1

3

リクエストの ServicePoint を取得し、ConnectionLimit を設定する必要があります

ServicePoint sp = ftpRequest.ServicePoint;
sp.ConnectionLimit = 10;

デフォルトの ConnectionLimit は 2 です。そのため、この動作が見られます。

更新:より完全な説明については、この回答を参照してください:

FtpWebRequest のパフォーマンスを改善するには?

于 2010-12-12T23:00:38.743 に答える