2010-11-24 33 views
4

Je travaille sur une opération asynchrone qui doit appeler d'autres tâches asynchrones. J'essaie de garder les choses simples en utilisant BackgroundWorkers, avec pour résultat qu'un callback DoWork() de BackgroundWorker appelle une méthode qui crée un second BackgroundWorker, comme ça (sans vérification d'erreur et tout ce jazz pour la brièveté):BackgroundWorkers imbriqués: appels RunWorkerCompleted sur le mauvais thread?

class Class1 
{ 
    private BackgroundWorker _worker = null; 

    public void DoSomethingAsync() 
    { 
     _worker = new BackgroundWorker(); 
     _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_worker_RunWorkerCompleted); 
     _worker.DoWork += new DoWorkEventHandler(_worker_DoWork); 
     _worker.RunWorkerAsync(); 
    } 

    void _worker_DoWork(object sender, DoWorkEventArgs e) 
    { 
     Class2 foo = new Class2(); 
     foo.DoSomethingElseAsync(); 
     while(foo.IsBusy) Thread.Sleep(0); // try to wait for foo to finish. 
    } 

    void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
    { 
     // do stuff 
    } 
} 

class Class2 
{ 
    private BackgroundWorker _worker = null; 
    Thread _originalThread = null; 

    public AsyncCompletedEventHandler DoSomethingCompleted; 

    public bool IsBusy { get { return _worker != null && _worker.IsBusy; } } 

    public void DoSomethingElseAsync() 
    { 
     _originalThread = Thread.CurrentThread; 

     _worker = new BackgroundWorker(); 
     _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_worker_RunWorkerCompleted); 
     _worker.DoWork += new DoWorkEventHandler(_worker_DoWork); 
     _worker.RunWorkerAsync(); 
    } 

    void _worker_DoWork(object sender, DoWorkEventArgs e) 
    { 
     // do stuff 
    } 

    void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
    { 
     Debug.Assert(Thread.CurrentThread == _originalThread); // fails 

     // Assuming the above Assert() were excluded, the following event would be raised on the wrong thread. 
     if (DoSomethingCompleted != null) DoSomethingCompleted(this, new AsyncCompletedEventArgs(e.Error, e.Cancelled, null)); 
    } 
} 

Donc, le problème est, je m'attends à ce que Class2._Worker_RunWorkerCompleted() s'exécute sur le même thread sur lequel Class2.DoSomethingElseAsync() a été appelé. Cela n'arrive jamais - à la place, le rappel fonctionne sur un thread complètement nouveau.

Voici ma suspicion: _worker_DoWork() de Class1 ne revient jamais, ce qui signifie que le thread ne reviendrait jamais à un écouteur d'événement, même s'il existait (je soupçonne que non). D'un autre côté, si _worker_DoWork() revenait, BackgroundWorker de Class1 finirait automatiquement prématurément - il doit attendre que Class2 ait fini de fonctionner avant de pouvoir terminer son travail.

Cela conduit à deux questions:

  1. je soupçonne pas?
  2. Quelle est la meilleure façon d'imbriquer des opérations asynchrones comme celle-ci? Puis-je récupérer l'approche BackgroundWorker ou y a-t-il une autre technique plus appropriée?

Répondre

5

Si un BackgroundWorker est créé sur le thread de l'interface utilisateur, DoWork s'exécutera sur un thread de pool de threads et RunWorkerCompleted s'exécutera sur le thread de l'interface utilisateur.

Si un BackgroundWorker est créé sur un thread d'arrière-plan (non le thread d'interface utilisateur) DoWork continue à fonctionner sur un fil de pool de threads et RunWorkerCompleted exécutera également sur un fil de pool de threads. Dans votre cas, étant donné que vous ne pouvez pas appeler un thread arbitraire (pool de threads), vous ne pourrez pas garantir le comportement que vous souhaitez, bien que vous souhaitiez regarder System.Threading.SynchronizationContext.

+0

Merci, cela m'a définitivement pointé dans la bonne direction. Il s'avère que je devrais implémenter le "modèle asynchrone basé sur l'événement" (http://msdn.microsoft.com/en-us/library/wewwczdw%28v=VS.90%29.aspx) pour gérer ce travail. Il utilise la classe AsyncOperation au lieu de BackgroundWorker, ce qui donne un certain contrôle sur le contexte de synchronisation auquel le message est destiné. –

0

Tout d'abord, je ne vois nulle part qui commence en fait en cours d'exécution du travailleur. Vous pouvez changer la méthode DoSomethingAsync (ajouter également l'appel à la méthode DoSomethingElseAsync dans Class2)

public void DoSomethingAsync() 
{ 
    _worker = new BackgroundWorker(); 
    _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_worker_RunWorkerCompleted); 
    _worker.DoWork += new DoWorkEventHandler(_worker_DoWork); 
    _worker.RunWorkerAsync(); // add this line to start it 
} 

En second lieu, le gestionnaire de travail (la méthode de _worker_DoWork) n'est pas garanti d'être sur le même fil que l'appel à DoSomethingAsync - C'est tout le point de l'arrière-plan. c'est à dire/de travailler sur un autre thread. La même chose s'applique pour le gestionnaire complet du worker (la méthode _worker_RunWorkerCompleted).

Enfin, il ne semble pas logique d'attacher les deux différents travailleurs d'arrière-plan à moins que le premier niveau (Classe1) nécessite toujours un travail de Classe2. Vous feriez mieux d'avoir un seul gestionnaire pour gérer chaque travailleur de fond.

+0

Désolé, j'ai oublié de taper les appels RunWorkerAsync(), mais ils sont présents dans le code réel.Le travailleur extérieur a besoin de contrôler le second; Le code permettant de déterminer quand et comment le travail du travailleur interne doit être effectué se produit dans la méthode DoWork du travailleur externe. –