2010-12-12 34 views
4

J'essaie de créer une collection de requêtes Web FTP pour télécharger une collection de fichiers.ThreadPool avec plusieurs threads créant des requêtes FTP expire

fonctionnait correctement dans un thread unique, mais j'essaie de faire avec plusieurs threads maintenant, mais je reçois une exception de délai. Je pense que je manque quelque chose assez simple mais ne peut pas sembler travailler dehors

Voici le code:

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

Il semble télécharger les deux premiers fichiers (en utilisant deux fils je suppose), mais puis timeout semble se produire lorsque ces complets et application tente de passer au fichier suivant.

Je peux confirmer que le FTPWebRequest qui expire est valide et le fichier existe, je pense que je peux avoir une connexion ouverte ou quelque chose.


allait poster un commentaire mais probablement plus facile à lire dans une réponse:

Tout d'abord, si je mets la propriété ftpRequest.Timout à Timeout.Infinite, la question du délai d'attente disparaît toutefois avoir un délai infini Ce n'est probablement pas la meilleure pratique. Donc, je préfère aller sur la résolution de cette autre façon ...

Débogage le code, je peux voir que quand il arrive à:

ThreadPool.QueueUserWorkItem(threadedFtpDownloader.PerformFtpRequest, localLogsFolder); 

Il entre dans la méthode PerformFtpRequest pour chaque requête Web FTP et appelle ftpWebRequest.GetResponse(), mais ne progresse ensuite que pour les deux premières requêtes. Le reste des demandes reste actif mais n'allez pas plus loin jusqu'à ce que les deux premières finissent. Cela signifie donc qu'ils sont laissés ouverts en attendant que les autres demandes soient terminées avant de commencer.

Je pense que la solution à ce problème consisterait à permettre à toutes les requêtes de s'exécuter immédiatement (la propriété ConnectionLimit n'a aucun effet ici) ou d'empêcher l'exécution de GetResponse jusqu'à ce qu'il soit prêt à utiliser la réponse.

Toutes les bonnes idées sur la meilleure façon de résoudre ce problème? Pour le moment, tout ce que je peux penser est des solutions hacky que je voudrais éviter :)

Merci!

Répondre

3

Vous devriez obtenir le ServicePoint pour la demande et régler la ConnectionLimit

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

Le ConnectionLimit par défaut est 2 - c'est pourquoi vous voyez ce comportement.

MISE À JOUR: Voir cette réponse pour une explication plus approfondie:

How to improve the Performance of FtpWebRequest?

+0

Merci compagnon. J'ai essayé d'ajouter ce code dans la boucle foreach qui créait FtpWebRequests mais obtenait le même problème. Est-ce là où je devrais définir la limite de connexion? – timothyclifford

+0

Ajout d'un lien vers une question/réponse avec plus de détails. ServicePoint est l'une de ces choses dont vous n'avez jamais entendu parler jusqu'à ce que vous vous cogniez la tête à quelques reprises. –