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);
        }
    }
}

它似乎正在下载前两个文件(我假设使用两个线程),但是当这些文件完成并且应用程序尝试移动到下一个文件时,似乎会发生超时。

我可以确认超时的 FTPWebRequest 是有效的并且文件存在,我想我可能有一个打开的连接或其他东西。


打算发表评论,但在答案中可能更容易阅读:

首先,如果我将 ftpRequest.Timout 属性设置为 Timeout.Infinite,超时问题就会消失,但是无限超时可能不是最佳实践。所以我宁愿用另一种方式解决这个问题......

调试代码,我可以看到当它到达时:

ThreadPool.QueueUserWorkItem(threadedFtpDownloader.PerformFtpRequest, localLogsFolder);

它为每个 FTP Web 请求进入 PerformFtpRequest 方法并调用 ftpWebRequest.GetResponse() 但随后仅对前两个请求进行进一步处理。其余请求保持活动状态,但在前两个完成之前不要继续。所以这基本上意味着它们在开始之前等待其他请求完成时保持打开状态。

我认为这个问题的解决方案要么是允许所有请求一次执行(ConnectionLimit 属性在这里无效),要么阻止执行调用 GetResponse,直到它真正准备好使用响应。

关于解决这个问题的最佳方法有什么好主意吗?目前我似乎能想到的只是我想避免的 hacky 解决方案:)

谢谢!

4

1 回答 1

3

您应该获取请求的 ServicePoint 并设置 ConnectionLimit

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

默认的 ConnectionLimit 是 2——这就是你看到这种行为的原因。

更新:有关更详尽的解释,请参阅此答案:

如何提高 FtpWebRequest 的性能?

于 2010-12-12T23:00:38.743 回答