2008-12-02 3 views
25

Ich habe meinen Haupt-GUI-Thread und einen zweiten Thread, der in seinem eigenen ApplicationContext läuft (um ihn am Leben zu erhalten, auch wenn es keine Arbeit gibt). Ich möchte eine Methode auf meinem zweiten Thread von meinem GUI-Thread aufrufen, aber wenn ich nur thread.Method() aufrufen; Es scheint auf meinem Haupt-GUI-Thread ausgeführt zu werden und führt dazu, dass meine GUI nicht mehr reagiert. Was ist der beste Weg, Methoden in verschiedenen Threads aufzurufen?In C# Wie wird empfohlen, Daten zwischen 2 Threads weiterzuleiten?

Update: Was ich hier wirklich suchen ist Kommunikation zwischen 2 Threads, nicht mit einer GUI kommunizieren. Die GUI ist nur einer der Threads, die mit meinem zweiten Thread kommunizieren müssen.

Update # 2: Ok, ich muss wirklich etwas verpassen. Ich habe ein Ereignis und einen Delegaten erstellt und ließ meinen Worker-Thread das Ereignis abonnieren. Aber wenn ich Invoke aufrufen (MyEvent); Von meinem GUI thread die Arbeit, die der Worker-Thread tut, ist auf dem GUI-Thread und hängt den GUI-Thread, bis es fertig ist Verarbeitung. Ist das, was ich versuche, sogar möglich, ohne ein statisches Objekt abzufragen?

Antwort

12

. Net kommt schon wit h eine System.ComponentModel.BackgroundWorker Klasse speziell für die Durchführung von Hintergrundaufgaben und die Kommunikation mit einer GUI. Benutze es.

+1

Ich versuche nicht, mit der GUI von meinem zweiten Thread zu kommunizieren, ich versuche, mit meinem Thread von der GUI zu kommunizieren. –

+0

"Kommunikation mit einer GUI" bedeutet zweiseitig, die GUI kann öffentliche Methoden des BackgroundWorker-Threads aufrufen. –

+0

Ja, aber das wird nicht wirklich "mit dem Hintergrund Thread kommunizieren" –

6

Im Endeffekt haben Sie die Version eines ThreadPool eines armen Mannes erstellt. Dein zweiter Thread sitzt einfach da und macht nichts und ohne eine Menge Arbeit von deiner Seite, kannst du es nicht einfach dazu bringen, Arbeit für dich zu tun. Sie müssten Delegaten in eine Warteschlange übergeben, die Ihr Thread dann ausführt und ausführt.

Ihre beste Wette ist, zu tun, was Sie vorhatten und nur den .NET ThreadPool verwenden und ihm Arbeit geben zu tun.

1

Verwenden Sie ein Synchronisationsobjekt, um dem Thread zu signalisieren, dass er die neuen Daten (oder den neuen Status der GUI) verarbeiten muss. Eine relativ einfache Möglichkeit ist das Verwenden eines Ereignisobjekts. Hier ist eine heruntergekommene, wie das funktionieren würde:

  1. GUI Benutzer 2. Thread Aktie ein Ereignisobjekt Thread (also wissen sie beide darüber)
  2. 2. Faden typischerweise läuft in einer Schleife von einer Art, und jeder Zeit, die für das Ereignis wartet signalisiert
  3. GUI-Thread signalisiert das Ereignis werden, wenn es die 2. Faden braucht etwas
  4. wenn das zweite Thread zu tun getan wird, es das Ereignis zurücksetzt und wartet erneut (oder Ausgänge)
+1

Sie müssen eine Art von Warteschlange verwenden, um die Daten zu platzieren - anderenfalls könnte der erste Thread Daten an den zweiten Thread senden, aber er verarbeitet immer noch das erste Datenelement. Sie müssen die Warteschlange ebenfalls sperren, damit sie nicht gleichzeitig darauf zugreifen. – gbjbaanb

1

Setzen Sie eine Schleife in Ihren zweiten Thread, der die meiste Zeit schläft, aber jedes [Intervall] wacht auf und überprüft eine gemeinsam genutzte Variable, die angibt, ob die Methode ausgeführt werden soll oder nicht und ob dieser geteilte Boolean gesetzt ist zu true, dann führt es eine Methode aus, die jede Aufgabe ausführt, die Sie ausführen möchten ... Lassen Sie die Methode in dieser Methode die erforderlichen Daten von einer anderen gemeinsam genutzten Variablen sammeln.

In Haupt-GUI-Thread, stellen die Daten in die Methodenparameter Variable geteilt und dann den boolean "Run" shared Variable auf true ...

Im Inneren des Arbeiter-Methode, erinnere mich an den gemeinsamen Bool „run zurücksetzen "Variable zu false, wenn Sie fertig sind, so dass die Schleife gewinnt; t die gleiche Instanz immer wieder ...

+0

Das würde funktionieren, aber ich brauche den Methodenaufruf sofort zu sein. Es müsste immer 1ms oder sowas abfragen, was ein bisschen viel wäre. –

+0

Dann muss nur der wartende Thread kontinuierlich laufen. Was Ihre Bedenken bezüglich "ein bisschen viel" betrifft, so verstehen Sie, dass der wartende Thread nur dann antworten kann, wenn er vom Betriebssystem eine Zeitlupe erhalten hat, und das kann nur geschehen, wenn der Code in der Thread ist am Leben –

+0

Brians Antwort für den besten Weg, dies ohne Abfrage für eine gemeinsame Variable zu tun. – gbjbaanb

3

Ich nehme an, dass ein Ereignis in der GUI erfordert, dass einige lang laufende Aufgabe gestartet werden, die ausgeführt werden im Hintergrund - es gibt zwei Möglichkeiten, dies zu tun.Wenn Sie einfach eine Methode für einen anderen Thread aufrufen möchten, können Sie dies unter Calling Synchronous Methods Asynchronously tun. Normalerweise mache ich so etwas wie dieses:



//delegate with same prototype as the method to call asynchrously 
delegate void ProcessItemDelegate(object item); 

//method to call asynchronously 
private void ProcessItem(object item) { ... } 

//method in the GUI thread 
private void DoWork(object itemToProcess) 
{ 
    //create delegate to call asynchronously... 
    ProcessItemDelegate d = new ProcessItemDelegate(this.ProcessItem); 
    IAsyncResult result = d.BeginInvoke(itemToProcess, 
             new AsyncCallback(this.CallBackMethod), 
             d); 
} 

//method called when the async operation has completed 
private void CallbackMethod(IAsyncResult ar) 
{ 
    ProcessItemDelegate d = (ProcessItemDelegate)ar.AsyncState; 
    //EndInvoke must be called on any delegate called asynchronously! 
    d.EndInvoke(ar); 
} 

Beachten Sie, wenn Sie diese Methode verwenden, die der Rückruf auf dem Hintergrund-Thread ausgeführt wird, so dass alle Aktualisierungen der GUI müssen mit Invoke erfolgen.

Alternativ können Sie den freigegebenen Status für die Kommunikation zwischen Threads verwenden und EventWaitHandle verwenden, um Updates für den freigegebenen Status zu signalisieren. In diesem Beispiel fügt eine Methode in der GUI Arbeitselemente zu einer Warteschlange hinzu, die im Hintergrund verarbeitet werden soll. Der Arbeitsthread verarbeitet Elemente aus der Warteschlange, wenn Arbeit verfügbar wird.


//shared state 
private Queue workQueue; 
private EventWaitHandle eventHandle; 

//method running in gui thread 
private void DoWork(Item itemToProcess) 
{ 
    //use a private lock object instead of lock... 
    lock(this.workQueue) 
    { 
     this.workQueue.Add(itemToProcess); 
     this.eventHandle.Set(); 
    } 
} 

//method that runs on the background thread 
private void QueueMonitor() 
{ 
    while(keepRunning) 
    { 
     //if the event handle is not signalled the processing thread will sleep here until it is signalled or the timeout expires 
     if(this.eventHandle.WaitOne(optionalTimeout)) 
     { 
      lock(this.workQueue) 
      { 
       while(this.workQueue.Count > 0) 
       { 
       Item itemToProcess = this.workQueue.Dequeue(); 
       //do something with item... 
       } 
      } 
      //reset wait handle - note that AutoResetEvent resets automatically 
      this.eventHandle.Reset(); 
     } 
    } 
} 
+0

Da ist eigentlich eine Race Condition. Wenn Sie einen DoWork-Anruf zwischen der Sperröffnung im QueueMonitor und dem Ereignis-Reset ausführen, verpassen Sie ein Ereignis und können erst nach dem nächsten DoWork oder der Zeitüberschreitung entsperren. – Jurney

2

Der Komfort von Control.BeginInvoke() ist schwer zu verlieren. Du musst nicht. eine neue Klasse zu Ihrem Projekt hinzufügen und diesen Code einfügen:

using System; 
using System.Threading; 
using System.Windows.Forms; 

public partial class frmWorker : Form { 
    public frmWorker() { 
    // Start the worker thread 
    Thread t = new Thread(new ParameterizedThreadStart(WorkerThread)); 
    t.IsBackground = true; 
    t.Start(this); 
    } 
    public void Stop() { 
    // Synchronous thread stop 
    this.Invoke(new MethodInvoker(stopWorker), null); 
    } 
    private void stopWorker() { 
    this.Close(); 
    } 
    private static void WorkerThread(object frm) { 
    // Start the message loop 
    frmWorker f = frm as frmWorker; 
    f.CreateHandle(); 
    Application.Run(f); 
    } 
    protected override void SetVisibleCore(bool value) { 
    // Shouldn't become visible 
    value = false; 
    base.SetVisibleCore(value); 
    } 
} 

Hier einige Beispiel-Code testen:

public partial class Form1 : Form { 
    private frmWorker mWorker; 
    public Form1() { 
     InitializeComponent(); 
     mWorker = new frmWorker(); 
    } 

    private void button1_Click(object sender, EventArgs e) { 
     Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId); 
     mWorker.BeginInvoke(new MethodInvoker(RunThisOnThread)); 
    } 
    private void RunThisOnThread() { 
     Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId); 
    } 

    private void button2_Click(object sender, EventArgs e) { 
     mWorker.Stop(); 
    } 
    } 
36

Wow, ich kann nicht glauben, wie die Menschen das Lesen nicht die Mühe kann Frage.

Wie auch immer, das ist was ich tue.

  1. Erstellen Sie "Nachrichten" -Klassen. Dies speichert alle Informationen, die Sie teilen möchten. Erstellen Sie eine Warteschlange <T> für jeden Thread. Verwenden Sie eine SyncLock (C# -Sperre) zum Lesen/Schreiben.
  2. Wenn Sie mit einem Thread sprechen möchten, senden Sie ihm ein Nachrichtenobjekt mit einer Kopie aller erforderlichen Informationen, indem Sie die Nachricht zur Warteschlange hinzufügen.
  3. Der Worker-Thread kann dann aus der Warteschlange lesen und jede Nachricht der Reihe nach lesen und verarbeiten. Wenn keine Nachrichten vorhanden sind, einfach schlafen.

Stellen Sie sicher, dass Sie keine Objekte zwischen den beiden Threads teilen. Sobald Ihr GUI-Thread eine Nachricht in der Warteschlange festhält, besitzt der GUI-Thread die Nachricht nicht länger. Es kann keinen Hinweis auf die Nachricht enthalten, sonst geraten Sie in Schwierigkeiten.

Dies gibt Ihnen nicht die bestmögliche Leistung, aber es wird für die meisten Anwendungen gut genug sein. Und was noch wichtiger ist: Es wird viel schwieriger, einen Fehler zu machen.

UPDATE: Verwenden Sie keine SyncLock und Warteschlange. Verwenden Sie stattdessen eine ConcurrentQueue, die automatisch jede Sperre für Sie übernimmt. Sie erhalten eine bessere Leistung und machen weniger Fehler.

+2

Ich bin überrascht, dass die andere Antwort als Antwort markiert ist. Die von Jonathon beschriebene Methode hat sich für mich immer als viel effektiver erwiesen. Stellen Sie sicher, dass die Warteschlange aus offensichtlichen Gründen nicht länger als für Enqueue() oder Dequeue() gesperrt wird. Auf ein kleines Nebenthema, Ich habe auch gefunden, diese Methode äußerst effektiv in der Netzwerkkommunikation mit drei Threads (Senden, Empfangen und Verarbeiten) und zwei Warteschlangen (Empfangen => Verarbeitung und Verarbeitung => Senden). – Derek

1

Sie können Ereignisse verwenden oder wie Grauenwolf sagte - eine Nachricht Cue. Ich wickle jeden meiner Threads als Management Singleton ein, von da aus kann man das entweder problemlos umsetzen. Du könntest sogar öffentliche Objekte des armen Mannes machen, um Bits zu drehen.

Sie auch eine Zustandsmaschine implementieren könnte, und statt Pass Nachrichten jeder Thread konnten einander

+0

Mir gefiel der Vorschlag von Grauenwolf und endete damit, eine Kombination aus dieser und der akzeptierten Antwort zu machen. –

6

Geck überwachen, lesen Sie kostenlose ebook Albahari .Net Threading. Ich bin damit verbunden, also ist dies kein Stecker. Ich habe es gelesen, hatten meine Mitarbeiter es gelesen, und ich habe es oft verwendet.

Ich würde empfehlen, eine Producer/Consumer-Klasse zu erstellen, in der Sie einen einzelnen Thread starten können, der wartet (nicht blockierend), Aufgaben in die Warteschlange einreiht und ihm signalisiert, zu arbeiten.

google einfach dafür.

+0

Lass es einfach hier: http://www.albahari.com/threading/ – matterai