2008-09-23 8 views
10

Quelle est la bonne technique d'avoir ThreadA le signal ThreadB d'un événement, sans avoir ThreadB sit bloqué en attendant de se produire un événement? J'ai un thread d'arrière-plan qui va remplir une liste partagée <T>. J'essaie de trouver un moyen de signaler de manière asynchrone le fil "principal" qu'il y a des données disponibles pour être collectées..NET: Comment avoir des données de fil principal de signal de fil de fond est disponible?


i envisagé de mettre un événement avec un objet EventWaitHandle, mais je ne peux pas avoir mon fil conducteur assis à une Event.WaitOne().


i considéré avoir un rappel de délégué, mais a) Je ne veux pas le thread principal faisant le travail dans le délégué: le fil doit se remettre au travail en ajoutant plus de choses - je ne veux pas attendre que le délégué s'exécute, et b) le délégué doit être rassemblé sur le thread principal, mais je ne suis pas en cours d'exécution d'une interface utilisateur, je n'ai aucun contrôle pour. Invoquer le délégué contre.


i considéré ont un rappel délégué qui commence simplement un intervalle de zéro System.Windows.Forms.Timer (avec accès du fil à l'synchronisé de la minuterie). De cette façon, le fil n'a besoin que d'être coincé comme il appelle

Timer.Enabled = true;

mais qui semble comme un hack.

Dans les temps anciens mon objet aurait créé une fenêtre cachée et avait le message de messages postaux à HWND de ces fenêtres cachées. J'ai envisagé de créer un contrôle caché, mais je comprends que vous ne pouvez pas .Invoke sur un contrôle sans aucun handle créé. De plus, je n'ai pas d'interface utilisateur: mon objet aurait pu être créé sur un serveur web, un service ou une console, je ne veux pas qu'un contrôle graphique apparaisse - et je ne veux pas non plus compiler une dépendance sur System.Windows. Formes.


je considérais avoir mon objet d'exposer une interface ISynchronizeInvoke, mais je aurais besoin de mettre en œuvre .invoke(), et c'est mon problème.


Quelle est la bonne technique d'avoir un fil fil de signal B d'un événement, sans fil B sit bloqué en attente d'un événement se produise?

Répondre

10

Voici un exemple de code pour la classe System.ComponentModel.BackgroundWorker.

private static BackgroundWorker worker = new BackgroundWorker(); 
    static void Main(string[] args) 
    { 
     worker.DoWork += worker_DoWork; 
     worker.RunWorkerCompleted += worker_RunWorkerCompleted; 
     worker.ProgressChanged += worker_ProgressChanged; 
     worker.WorkerReportsProgress = true; 

     Console.WriteLine("Starting application."); 
     worker.RunWorkerAsync(); 

     Console.ReadKey(); 
    } 

    static void worker_ProgressChanged(object sender, ProgressChangedEventArgs e) 
    { 
     Console.WriteLine("Progress."); 
    } 

    static void worker_DoWork(object sender, DoWorkEventArgs e) 
    { 
     Console.WriteLine("Starting doing some work now."); 

     for (int i = 0; i < 5; i++) 
     { 
      Thread.Sleep(1000); 
      worker.ReportProgress(i); 
     } 
    } 

    static void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
    { 
     Console.WriteLine("Done now."); 
    } 
1

Si vous utilisez un arrière-plan pour démarrer le deuxième thread et utilisez l'événement ProgressChanged pour notifier l'autre thread que les données sont prêtes. D'autres événements sont également disponibles. THis MSDN article should get you started.

+0

la classe BackgroundWorker semble être la seule chose qui peut envoyer un NOTIFICATI de manière asynchrone sur le * thread * qui a créé l'objet. En interne, il utilise l'objet AsyncOperation en appelant asyncOperation.Post(). –

1

Il existe plusieurs façons de le faire, en fonction de ce que vous voulez faire exactement. Un producer/consumer queue est probablement ce que vous voulez. Pour un excellent aperçu des threads, voir le chapitre sur Threading (disponible en ligne) de l'excellent livre C# 3.0 in a Nutshell.

+0

Dans le modèle producteur/consommateur, le thread principal crée les unités de travail à exécuter. Un fil de fond se trouve en attente de travail être mis en file d'attente. Lorsqu'une unité de travail est créée et qu'un événement est défini. Cela ne fonctionne pas ici puisque le sujet principal est le "consommateur", et il ne peut pas s'asseoir attendre le travail. –

1

Vous pouvez utiliser un AutoResetEvent (ou ManualResetEvent). Si vous utilisez AutoResetEvent.WaitOne (0, false), il ne bloquera pas.Par exemple:

AutoResetEvent ev = new AutoResetEvent(false); 
... 
if(ev.WaitOne(0, false)) { 
    // event happened 
} 
else { 
// do other stuff 
} 
+0

Comment vérifiez-vous si l'événement est signalé? c'est-à-dire quand? –

0

Si votre thread « principal » est le fil conducteur pompe (GUI) de message Windows, vous pouvez alors interroger l'aide d'un Forms.Timer - régler l'intervalle de minuterie en fonction de la rapidité avec laquelle vous devez avoir votre interface graphique thread 'notez' les données du thread de travail. N'oubliez pas de synchroniser l'accès au List<> partagé si vous utilisez foreach, pour éviter les exceptions CollectionModified.

J'utilise cette technique pour toutes les mises à jour de GUI basées sur les données du marché dans une application de trading en temps réel, et cela fonctionne très bien.

3

Je combine ici quelques réponses.

La situation idéale utilise un indicateur de type thread-safe tel qu'un AutoResetEvent. Vous n'avez pas à bloquer indéfiniment lorsque vous appelez WaitOne(), en fait, il a une surcharge qui vous permet de spécifier un délai d'expiration. Cette surcharge renvoie false si l'indicateur n'a pas été défini pendant l'intervalle.

Un Queue est une structure plus idéale pour une relation producteur/consommateur, mais vous pouvez l'imiter si vos exigences vous obligent à utiliser un List. La principale différence est que vous devrez vous assurer que votre client verrouille l'accès à la collection pendant l'extraction des éléments. le plus sûr est probablement d'utiliser la méthode CopyTo pour copier tous les éléments dans un tableau, puis de relâcher le verrou. Bien sûr, assurez-vous que votre producteur n'essaiera pas de mettre à jour le List tant que le verrou est maintenu.

Voici une simple application console C# qui montre comment cela pourrait être implémenté. Si vous jouez avec les intervalles de temps, vous pouvez provoquer diverses choses; Dans cette configuration particulière, j'essayais que le producteur génère plusieurs articles avant que le consommateur ne vérifie les articles.

using System; 
using System.Collections.Generic; 
using System.Threading; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     private static object LockObject = new Object(); 

     private static AutoResetEvent _flag; 
     private static Queue<int> _list; 

     static void Main(string[] args) 
     { 
      _list = new Queue<int>(); 
      _flag = new AutoResetEvent(false); 

      ThreadPool.QueueUserWorkItem(ProducerThread); 

      int itemCount = 0; 

      while (itemCount < 10) 
      { 
       if (_flag.WaitOne(0)) 
       { 
        // there was an item 
        lock (LockObject) 
        { 
         Console.WriteLine("Items in queue:"); 
         while (_list.Count > 0) 
         { 
          Console.WriteLine("Found item {0}.", _list.Dequeue()); 
          itemCount++; 
         } 
        } 
       } 
       else 
       { 
        Console.WriteLine("No items in queue."); 
        Thread.Sleep(125); 
       } 
      } 
     } 

     private static void ProducerThread(object state) 
     { 
      Random rng = new Random(); 

      Thread.Sleep(250); 

      for (int i = 0; i < 10; i++) 
      { 
       lock (LockObject) 
       { 
        _list.Enqueue(rng.Next(0, 100)); 
        _flag.Set(); 
        Thread.Sleep(rng.Next(0, 250)); 
       } 
      } 
     } 
    } 
} 

Si vous ne voulez pas du tout bloquer le producteur, c'est un peu plus compliqué. Dans ce cas, je suggère de faire au producteur sa propre classe avec un tampon privé et public et un public AutoResetEvent. Le producteur stockera par défaut les éléments dans le tampon privé, puis essaiera de les écrire dans le tampon public. Lorsque le consommateur travaille avec le tampon public, il réinitialise le drapeau sur l'objet producteur. Avant que le producteur ne tente de déplacer des éléments du tampon privé vers le tampon public, il vérifie cet indicateur et ne copie les éléments que lorsque le consommateur ne travaille pas dessus.

+0

La file d'attente semble être une classe intéressante à utiliser. Bien que je ne le veuille pas dans ma situation depuis un certain temps, l'utilisateur peut gérer la liste entière en une fois, plutôt que de devoir mettre en file d'attente des éléments individuels. –

1

La classe BackgroundWorker est la réponse dans ce cas. C'est la seule construction de threads capable d'envoyer de manière asynchrone des messages au thread qui a créé l'objet BackgroundWorker. En interne, la classe AsyncOperation utilise la méthode asyncOperation.Post().

this.asyncOperation = AsyncOperationManager.CreateOperation(null); 
this.asyncOperation.Post(delegateMethod, arg); 

Quelques autres classes dans le framework .NET utilisent également AsyncOperation:

  • BackgroundWorker
  • SoundPlayer.LoadAsync()
  • SmtpClient.SendAsync()
  • Ping.SendAsync()
  • WebClient.DownloadDataAsync()
  • WebClient.DownloadFile()
  • WebClient.DownloadFileAsync()
  • WebClient ...
  • PictureBox.LoadAsync()