2010-07-20 4 views
3

J'ai écrit une méthode qui télécharge des fichiers, et maintenant j'essaie de le faire télécharger jusqu'à 5 fichiers en parallèle, et pour le reste d'attendre que les précédents se terminent. J'utilise un ManualResetEvent pour cela, mais quand j'inclus la partie de syncronisation, il ne télécharge plus rien (sans que cela fonctionne).Problème de synchronisation avec ManualResetEvent

Voici le code des méthodes:

static readonly int maxFiles = 5; 
    static int files = 0; 
    static object filesLocker = new object(); 
    static System.Threading.ManualResetEvent sync = new System.Threading.ManualResetEvent(true); 

    /// <summary> 
    /// Download a file from wikipedia asynchronously 
    /// </summary> 
    /// <param name="filename"></param> 
    public void DoanloadFileAsync(string filename) 
    { 
     ... 
     System.Threading.ThreadPool.QueueUserWorkItem(
      (o) => 
      { 
       bool loop = true; 
       while (loop) 
        if (sync.WaitOne()) 
         lock (filesLocker) 
         { 
          if (files < maxFiles) 
          { 
           ++files; 
           if (files == maxFiles) 
            sync.Reset(); 
           loop = false; 
          } 
         } 
       try 
       { 
        WebClient downloadClient = new WebClient(); 
        downloadClient.OpenReadCompleted += new OpenReadCompletedEventHandler(downloadClient_OpenReadCompleted); 
        downloadClient.OpenReadAsync(new Uri(url, UriKind.Absolute)); 
        //5 of them do get here 
       } 
       catch 
       { 
        lock (filesLocker) 
        { 
         --files; 
         sync.Set(); 
        } 
        throw; 
       } 
      }); 
    } 

    void downloadClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e) 
    { 
     try 
     { 
      //but none of the 5 get here 
      ...Download logic... //works without the ManualResetEvent 
     } 
     finally 
     { 
      lock (filesLocker) 
      { 
       --files; 
       sync.Set(); 
      } 
     } 
    } 

que je fais quelque chose de mal?

Il est écrit avec Silverlight 4 pour Windows Phone 7.

Edit: Il n'y a pas Sémaphore ou SemaphoreSlim dans Silverlight 4.

+0

Pourquoi verrouillage lorsque vous pouvez utiliser la méthode 'System.Threading.Interlocked.Decrement()', etc.? –

+0

Parce que je veux aussi appeler sync.Set(), et je pense que quelqu'un appelle froid sync.Set() à partir d'un autre thread, puis je décrémente, certains threads incrémentent et appellent sync.Reset(), puis j'appelle sync.Set () et j'obtiens plus de téléchargements de threads maxFiles. – user182945

+1

Vérifie ma réponse, c'est ce que tu cherches. Aussi, en utilisant un événement de réinitialisation est OK, je ne vois tout simplement pas le besoin de verrous. Oh, j'utilise AutoResetEvent, car c'est exactement ce dont vous avez besoin ici. –

Répondre

4

Ce que je voulais dire dans mon commentaire était tout en utilisant un lent lock lorsque vous pouvez utiliser Interlocked. En outre, il sera beaucoup plus performant de cette façon.

au plus 5 téléchargements actifs en parallèle:

public class Downloader 
{ 
private int fileCount = 0; 
private AutoResetEvent sync = new AutoResetEvent(false); 

private void StartNewDownload(object o) 
{ 
    if (Interlocked.Increment(ref this.fileCount) > 5) this.sync.WaitOne(); 

    WebClient downloadClient = new WebClient(); 
    downloadClient.OpenReadCompleted += downloadClient_OpenReadCompleted; 
    downloadClient.OpenReadAsync(new Uri(o.ToString(), UriKind.Absolute)); 
} 

private void downloadClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e) 
{ 
    try 
    { 
    // Download logic goes here. 
    } 
    catch {} 
    finally 
    { 
    this.sync.Set(); 
    Interlocked.Decrement(ref this.fileCount); 
    } 
} 

public void Run() 
{ 
    string o = "url1"; 
    System.Threading.ThreadPool.QueueUserWorkItem(this.StartNewDownload, o); 
    Thread.Sleep(100); 

    o = "url2"; 
    System.Threading.ThreadPool.QueueUserWorkItem(this.StartNewDownload, o); 

    o = "url3"; 
    System.Threading.ThreadPool.QueueUserWorkItem(this.StartNewDownload, o); 
    Thread.Sleep(200); 

    o = "url4"; 
    System.Threading.ThreadPool.QueueUserWorkItem(this.StartNewDownload, o); 

    o = "url5"; 
    System.Threading.ThreadPool.QueueUserWorkItem(this.StartNewDownload, o); 

    o = "url6"; 
    System.Threading.ThreadPool.QueueUserWorkItem(this.StartNewDownload, o); 

    o = "url7"; 
    System.Threading.ThreadPool.QueueUserWorkItem(this.StartNewDownload, o); 
    Thread.Sleep(200); 

    o = "url8"; 
    System.Threading.ThreadPool.QueueUserWorkItem(this.StartNewDownload, o); 
    Thread.Sleep(400); 
} 
} 
+0

La classe aurait dû être statique (le code fonctionne également pour la synchronisation statique). –

+0

Oui, c'est plus performant de cette façon ... mais j'ai toujours le même problème ... peut-être qu'il ne provient pas de l'AutoResetEvent/ManualResetEvent. – user182945

+0

@ user182945: Avez-vous déjà essayé mon code? Il ne peut jamais atteindre plus de 5 téléchargements simultanés. Si vos rappels ne sont pas exécutés, le problème est peut-être ailleurs. Peut-être que c'est quelque chose dans votre logique de téléchargement. Je l'ai vide pour tester et ça marche bien. –

0

Au REGARDENT WaitOne() est frappé avant que vous créez le WebClient. Puisque tout le code qui appelle Set() se trouve dans le gestionnaire d'événements ou dans le gestionnaire d'exceptions, il ne sera jamais touché.

Peut-être vous par erreur inclus le code WebClient dans le pool de threads méthode de fil qui devrait être à l'extérieur de celui-ci

System.Threading.ThreadPool.QueueUserWorkItem(
     (o) => 
     { 
      bool loop = true; 
      while (loop) 
       if (sync.WaitOne()) 
        lock (filesLocker) 
        { 
         if (files < maxFiles) 
         { 
          ++files; 
          if (files == maxFiles) 
           sync.Reset(); 
          loop = false; 
         } 
        } 

     }); 

//Have the try catch OUTSIDE the background thread. 
      try 
      { 
       WebClient downloadClient = new WebClient(); 
       downloadClient.OpenReadCompleted += new OpenReadCompletedEventHandler(downloadClient_OpenReadCompleted); 
       downloadClient.OpenReadAsync(new Uri(url, UriKind.Absolute)); 
       //5 of them do get here 
      } 
      catch 
      { 
       lock (filesLocker) 
       { 
        --files; 
        sync.Set(); 
       } 
       throw; 
      } 
+0

Je veux créer le WebClient et appeler l'OpenReadAsync uniquement après avoir obtenu un fichier ++; pour l'appel DownloadAsync en cours. Ainsi, il ne téléchargera que maxFiles à la fois, et pour le reste, attendez la fin d'un des téléchargements précédents. (Je ne reçois pas une exception NullReferenceException pour cette partie lorsque je débogue, donc je suppose que c'est créé correctement). – user182945

1

Il semble que vous essayez de limiter le nombre de threads qui peuvent entrer dans votre section critique, le téléchargement du fichier, à la fois. Plutôt que d'essayer de le faire à la main, utilisez un System.Threading.Semaphore - c'est ce qu'il fait!

+0

Il ne semble pas y avoir de sémaphore dans Silverlight pour Windows Phone. Y at-il quelque chose d'autre similaire à un sémaphore que je peux utiliser? – user182945

+0

ahh - oui - désolé - peut-être pas. Je regardais la documentation pour le cadre complet. J'ai trouvé quelques liens utiles ici où les gens discutent des alternatives dans Silverlight: http://stackoverflow.com/questions/2307844/silverlight-dealing-with-async-calls et http://forums.silverlight.net/forums/ p/20199/69433.aspx –

+0

Rob est correct; vous utilisez la mauvaise primitive de synchronisation. Si vous utilisez .NET 4.0, utilisez 'System.Threading.SemaphoreSlim' à la place - il est léger, rapide et prend en charge les jetons d'annulation, une fonctionnalité que vous pourriez trouver utile lors d'opérations longues comme le téléchargement de fichiers. EDIT: A pris trop de temps pour taper ... –

Questions connexes