2008-09-23 6 views
10

Was ist die richtige Technik ThreadA Signal ThreadB eines Ereignisses zu haben, ohne dass ThreadB sitzen blockiert warten auf ein Ereignis geschehen?.NET: Wie sind Hintergrund-Thread-Signal-Haupt-Thread-Daten verfügbar?

Ich habe einen Hintergrund-Thread, der eine geteilte Liste <T> füllen wird. Ich versuche einen Weg zu finden, asynchron dem "Haupt" -Thread zu signalisieren, dass Daten zur Verfügung stehen.


i als ein Ereignis mit einem Eventwaithandle-Objekt festlegen, aber ich kann mein Haupt-Thread sitzt an einem Event.WaitOne() nicht haben.


i als einen Delegaten Rückruf haben, aber a) ich will nicht den Haupt-Thread Arbeit in den Delegierten zu tun: der Faden muss wieder an die Arbeit, um mehr Sachen Zugabe - ich will es nicht warten, während der Delegat ausgeführt wird, und b) der Delegat muss auf den Haupt-Thread marshalled sein, aber ich habe keine Benutzeroberfläche, ich habe keine Kontrolle zu .Legen Sie den Delegaten gegen.


i betrachtet haben einen Delegierten Rückruf, der einfach startet einen Null-Intervall System.Windows.Forms.Timer (mit Gewinde Zugriff auf den Zeitgeber synchronisiert). Auf diese Weise der Faden muss nur geklebt werden, wie es

nennt

Timer.Enabled = true;

aber das scheint wie ein Hack.

In den alten Tagen würde mein Objekt ein verstecktes Fenster erstellt haben und der Thread Nachrichten an diese versteckten Windows 'HWND senden. Ich überlegte, ein verstecktes Steuerelement zu erstellen, aber ich versichere, dass Sie nicht auf ein Steuerelement mit keinem Handle erstellen können. Außerdem habe ich keine Benutzeroberfläche: Mein Objekt könnte auf einem Webserver, einem Dienst oder einer Konsole erstellt worden sein, ich möchte nicht, dass ein grafisches Steuerelement erscheint - auch möchte ich keine Abhängigkeit von System.Windows kompilieren. Formen.


i betrachtet mein Objekt aussetzen eine ISynchronizeInvoke Schnittstelle, aber dann würde ich brauche .Invoke() zu implementieren, und das ist mein Problem.


Was die richtige Technik, um ein Signal Thread B eines Ereignisses zu haben Gewinde, ohne Gewinde B wartet blockiert sitzen mit für ein Ereignis geschehen?

Antwort

10

Hier ist ein Codebeispiel für die System.ComponentModel.BackgroundWorker-Klasse.

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

Wenn Sie einen Hintergrund-Worker verwenden, um den zweiten Thread zu starten, und das ProgressChanged-Ereignis verwenden, um den anderen Thread zu benachrichtigen, dass Daten bereit sind. Andere Ereignisse sind ebenfalls verfügbar. THis MSDN article should get you started.

+0

die Background Klasse scheint das einzige zu sein, die asynchron eine Notificati gesendet werden auf den * Thread *, der das Objekt erstellt hat. Intern verwendet es das AsyncOperation-Objekt, indem es asyncOperation.Post() aufruft. –

1

Es gibt viele Möglichkeiten, dies zu tun, je nachdem, was genau Sie tun möchten. A producer/consumer queue ist wahrscheinlich, was Sie wollen. Für einen exzellenten Einblick in Themen, siehe das Kapitel Threading (online verfügbar) aus dem ausgezeichneten Buch C# 3.0 in a Nutshell.

+0

Im Producer/Consumer-Modell erstellt der Hauptthread Arbeitseinheiten, die ausgeführt werden müssen. Ein Hintergrundfaden sitzt, wartend auf Arbeit, die in der Warteschlange ist. Wenn eine Arbeitseinheit erstellt und ein Ereignis festgelegt wird. Das funktioniert hier nicht, da der Hauptthread der "Konsument" ist und er nicht darauf warten kann, auf Arbeit zu warten. –

1

Sie können ein AutoResetEvent (oder ManualResetEvent) verwenden. Wenn Sie AutoResetEvent.WaitOne (0, false) verwenden, wird es nicht blockiert.Zum Beispiel:

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

Wie überprüfen Sie, ob das Ereignis signalisiert wird? d.h. wann? –

0

Wenn Ihr „Haupt“ Thread ist das Windows-Nachrichtensystem (GUI) Thread, dann können Sie abfragen, eine Forms.Timer mit - tune das Timer-Intervall je nachdem, wie schnell müssen Sie Ihre GUI haben thread "notieren" Sie die Daten aus dem Worker-Thread.

Denken Sie daran, den Zugriff auf die gemeinsame List<> zu synchronisieren, wenn Sie foreach verwenden möchten, um CollectionModified Ausnahmen zu vermeiden.

Ich verwende diese Technik für alle marktdatengesteuerten GUI-Updates in einer Echtzeit-Trading-Anwendung, und es funktioniert sehr gut.

3

Ich kombiniere ein paar Antworten hier.

Die ideale Situation verwendet eine thread-sichere Flagge wie AutoResetEvent. Sie müssen nicht unbegrenzt blockieren, wenn Sie WaitOne() aufrufen, in der Tat hat es eine Überladung, die es Ihnen ermöglicht, eine Zeitüberschreitung festzulegen. Diese Überladung gibt false zurück, wenn das Flag während des Intervalls nicht gesetzt wurde.

Eine Queue ist eine idealere Struktur für eine Erzeuger/Verbraucher-Beziehung, aber Sie können es nachahmen, wenn Ihre Anforderungen Sie zwingen, eine List zu verwenden. Der Hauptunterschied besteht darin, dass Sie sicherstellen müssen, dass Ihre Benutzer den Zugriff auf die Sammlung sperren, während sie Objekte extrahieren. Am sichersten ist es, die Methode CopyTo zu verwenden, um alle Elemente in ein Array zu kopieren und dann die Sperre aufzuheben. Stellen Sie sicher, dass Ihr Hersteller nicht versucht, die List zu aktualisieren, während das Schloss gehalten wird.

Hier ist eine einfache C# -Konsolenanwendung, die demonstriert, wie dies implementiert werden könnte. Wenn Sie mit den Timing-Intervallen herumspielen, können Sie verschiedene Dinge passieren lassen; In dieser speziellen Konfiguration habe ich versucht, dass der Produzent mehrere Artikel generiert, bevor der Verbraucher nach Artikeln sucht.

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

Wenn Sie den Produzenten überhaupt nicht blockieren wollen, ist es ein wenig komplizierter. In diesem Fall würde ich vorschlagen, dem Produzenten eine eigene Klasse mit einem privaten und einem öffentlichen Puffer und einem öffentlichen AutoResetEvent zu geben. Der Produzent speichert Elemente standardmäßig im privaten Puffer und versucht dann, sie in den öffentlichen Puffer zu schreiben. Wenn der Benutzer mit dem öffentlichen Puffer arbeitet, setzt er das Flag für das Producer-Objekt zurück. Bevor der Hersteller versucht, Elemente aus dem privaten Puffer in den öffentlichen Puffer zu verschieben, überprüft es dieses Flag und kopiert nur Elemente, wenn der Benutzer nicht daran arbeitet.

+0

Warteschlange scheint wie eine interessante Klasse zu verwenden. Obwohl ich es in meiner Situation nicht wollen würde, da der Benutzer irgendwann die gesamte Liste auf einmal bearbeiten kann, anstatt einzelne Einträge aus der Warteschlange zu nehmen. –

1

Die BackgroundWorker-Klasse ist in diesem Fall die Antwort. Es ist das einzige Threading-Konstrukt, das Nachrichten asynchron an den Thread senden kann, der das BackgroundWorker-Objekt erstellt hat. Intern BackgroundWorker verwendet die AsyncOperation Klasse durch Aufruf der asyncOperation.Post() Methode.

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

Ein paar andere Klassen im .NET Framework auch AsyncOperation verwenden:

  • Background
  • Soundplayer.LoadAsync()
  • SmtpClient.SendAsync()
  • Ping.SendAsync()
  • WebClient.DownloadDataAsync()
  • WebClient.DownloadFile()
  • WebClient.DownloadFileAsync()
  • WebClient ...
  • PictureBox.LoadAsync()
Verwandte Themen