ThreadPool с несколькими потоками, создающими запросы FTP - PullRequest
4 голосов
/ 13 декабря 2010

Я пытаюсь создать коллекцию веб-запросов FTP для загрузки коллекции файлов.

Работал правильно, делая это в одном потоке, но сейчас пытаюсь сделать это с несколькими потоками, но получаю исключение тайм-аута. Я думаю, что упускаю что-то довольно простое, но не могу решить это

Вот код:

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

Он входит в метод PerformFtpRequest для каждого веб-запроса FTP и вызывает ftpWebRequest.GetResponse (), но затем продвигается только дальше для первых двух запросов. Остальные запросы остаются активными, но не идут дальше, пока не закончат первые два. Таким образом, это в основном означает, что они остаются открытыми во время ожидания завершения других запросов перед началом.

Я думаю, что решением этой проблемы было бы либо одновременное выполнение всех запросов (свойство ConnectionLimit здесь не действует), либо предотвращение вызова GetResponse при выполнении до тех пор, пока он фактически не будет готов использовать ответ.

Есть хорошие идеи о том, как решить эту проблему? На данный момент все, о чем я могу думать, это хакерские решения, которых я бы хотел избежать:)

Спасибо!

1 Ответ

3 голосов
/ 13 декабря 2010

Вы должны получить ServicePoint для запроса и установить ConnectionLimit

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

По умолчанию ConnectionLimit равен 2 - вот почему вы видите такое поведение.

ОБНОВЛЕНИЕ: см. Этот ответ для более подробного объяснения:

Как повысить производительность FtpWebRequest?

...