.NETZ:Wie rufe ich einen Delegaten für einen bestimmten Thread auf?(ISynchronizeInvoke, Dispatcher, AsyncOperation, SynchronizationContext usw.)

StackOverflow https://stackoverflow.com/questions/4843010

Frage

Beachten Sie zunächst, dass diese Frage nicht getaggt ist oder oder irgendetwas anderes GUI-spezifisches.Das ist Absicht, wie Sie gleich sehen werden.

Zweitens: Entschuldigung, wenn diese Frage etwas lang ist.Ich versuche, verschiedene hier und da herumschwirrende Informationen zusammenzuführen, um auch wertvolle Informationen bereitzustellen.Meine Frage steht allerdings direkt unter „Was ich gerne wissen würde“.

Ich habe es mir zur Aufgabe gemacht, endlich die verschiedenen Möglichkeiten zu verstehen, die .NET bietet, um einen Delegaten für einen bestimmten Thread aufzurufen.


Was ich gerne wissen würde:

  • Ich suche nach der allgemeinsten Möglichkeit (die nicht Winforms- oder WPF-spezifisch ist), Delegaten für bestimmte Threads aufzurufen.

  • Oder anders ausgedrückt:Mich würde interessieren, ob und wie es verschiedene Möglichkeiten gibt, dies zu tun (z. B. über WPFs). Dispatcher) sich gegenseitig ausnutzen;Das heißt, wenn es einen gemeinsamen Mechanismus für den Thread-übergreifenden Aufruf von Delegaten gibt, der von allen anderen verwendet wird.


Was ich schon weiß:

  • Es gibt viele Kurse zu diesem Thema.darunter:

    • SynchronizationContext (In System.Threading)
      Wenn ich raten müsste, wäre das die einfachste;obwohl ich nicht verstehe, was genau es tut und wie es verwendet wird.

    • AsyncOperation & AsyncOperationManager (In System.ComponentModel)
      Das scheinen Hüllen zu sein SynchronizationContext.Keine Ahnung, wie man sie benutzt.

    • WindowsFormsSynchronizationContext (In System.Windows.Forms)
      Eine Unterklasse von SynchronizationContext.

    • ISynchronizeInvoke (In System.ComponentModel)
      Wird von Windows Forms verwendet.(Der Control Klasse implementiert dies.Wenn ich raten müsste, würde ich sagen, dass diese Implementierung nutzt WindowsFormsSynchronizationContext.)

    • Dispatcher &DispatcherSynchronizationContext (In System.Windows.Threading)
      Letzteres scheint eine weitere Unterklasse von zu sein SynchronizationContext, und die ehemaligen Delegierten dazu.

  • Einige Threads verfügen über eine eigene Nachrichtenschleife sowie eine Nachrichtenwarteschlange.

    (Die MSDN-Seite Über Nachrichten und Nachrichtenwarteschlangen enthält einige einführende Hintergrundinformationen zur Funktionsweise von Nachrichtenschleifen auf Systemebene, d. h.Nachrichtenwarteschlangen als Windows-API.)

    Ich kann mir vorstellen, wie man den Thread-übergreifenden Aufruf für Threads mit einer Nachrichtenwarteschlange implementieren würde.Mit der Windows-API würden Sie eine Nachricht über in die Nachrichtenwarteschlange eines bestimmten Threads stellen PostThreadMessage das eine Anweisung enthält, einen Delegaten aufzurufen.Die Nachrichtenschleife, die in diesem Thread ausgeführt wird, gelangt schließlich zu dieser Nachricht und der Delegat wird aufgerufen.

    Nach dem, was ich auf MSDN gelesen habe, verfügt ein Thread nicht automatisch über eine eigene Nachrichtenwarteschlange.Eine Nachrichtenwarteschlange wird verfügbar, z. B.wenn ein Thread ein Fenster erstellt hat.Ohne eine Nachrichtenwarteschlange macht es keinen Sinn, dass ein Thread eine Nachrichtenschleife hat.

    Ist der Aufruf eines Thread-übergreifenden Delegaten überhaupt möglich, wenn der Ziel-Thread keine Nachrichtenschleife hat?Sagen wir, in einer .NET-Konsolenanwendung?(Nach den Antworten auf zu urteilen diese Frage, ich nehme an, dass es mit Konsolen-Apps tatsächlich unmöglich ist.)

War es hilfreich?

Lösung

Entschuldigen Sie die so lange Antwort. Aber ich dachte, es lohnt sich zu erklären, was genau los ist.

A-ha! Ich glaube, ich habe es herausgefunden. Die allgemeinste Methode zum Aufrufen eines Delegaten in einem bestimmten Thread scheint in der Tat die SynchronizationContext-Klasse zu sein.

Erstens bietet das .NET-Framework keine Standardmethode zum einfachen "Senden" eines Delegaten an einen -Thread, sodass er dort sofort ausgeführt wird. Offensichtlich kann dies nicht funktionieren, da dies bedeuten würde, die Arbeit, die der Thread zu diesem Zeitpunkt ausführen würde, zu "unterbrechen". Daher entscheidet der Ziel-Thread selbst, wie und wann er Delegaten "empfängt". Das heißt, diese Funktionalität muss vom Programmierer bereitgestellt werden.

Ein Ziel-Thread benötigt also eine Möglichkeit, Delegierte zu "empfangen". Dies kann auf viele verschiedene Arten erfolgen. Ein einfacher Mechanismus besteht darin, dass der Thread immer zu einer Schleife zurückkehrt (nennen wir es die "Nachrichtenschleife"), in der er eine Warteschlange betrachtet. Es wird funktionieren, was auch immer in der Warteschlange steht. Windows funktioniert nativ wie folgt, wenn es um UI-bezogene Dinge geht.

Im Folgenden werde ich zeigen, wie eine Nachrichtenwarteschlange und ein SynchronizationContext dafür sowie ein Thread mit einer Nachrichtenschleife implementiert werden. Abschließend werde ich zeigen, wie ein Delegat in diesem Thread aufgerufen wird.


Beispiel:

Schritt 1. Erstellen wir zunächst eine SynchronizationContext-Klasse , die zusammen mit der Nachrichtenwarteschlange des Ziel-Threads verwendet wird:

class QueueSyncContext : SynchronizationContext
{
    private readonly ConcurrentQueue<SendOrPostCallback> queue;

    public QueueSyncContext(ConcurrentQueue<SendOrPostCallback> queue)
    {
        this.queue = queue;
    }

    public override void Post(SendOrPostCallback d, object state)
    {
        queue.Enqueue(d);
    }

    // implementation for Send() omitted in this example for simplicity's sake.
}

Grundsätzlich bedeutet dies nicht mehr, als alle Delegaten, die über Post übergeben werden, zu einer vom Benutzer bereitgestellten Warteschlange hinzuzufügen. (Post ist die Methode für asynchrone Aufrufe. Send wäre für synchrone Aufrufe. Letzteres lasse ich vorerst weg.)

Schritt 2. Schreiben wir nun den Code für einen Thread Z , der darauf wartet, dass der d der Delegierten eintrifft :

SynchronizationContext syncContextForThreadZ = null;

void MainMethodOfThreadZ()
{
    // this will be used as the thread's message queue:
    var queue = new ConcurrentQueue<PostOrCallDelegate>();

    // set up a synchronization context for our message processing:
    syncContextForThreadZ = new QueueSyncContext(queue);
    SynchronizationContext.SetSynchronizationContext(syncContextForThreadZ);

    // here's the message loop (not efficient, this is for demo purposes only:)
    while (true)
    {
        PostOrCallDelegate d = null;
        if (queue.TryDequeue(out d))
        {
            d.Invoke(null);
        }
    }
}

Schritt 3. Thread Z muss irgendwo gestartet werden:

new Thread(new ThreadStart(MainMethodOfThreadZ)).Start();

Schritt 4. Zum Schluss in einem anderen Thread A möchten wir einen Delegaten an den Thread Z senden:

void SomeMethodOnThreadA()
{
    // thread Z must be up and running before we can send delegates to it:
    while (syncContextForThreadZ == null) ;

    syncContextForThreadZ.Post(_ =>
        {
            Console.WriteLine("This will run on thread Z!");
        },
        null);
}


Das Schöne daran ist, dass SynchronizationContext funktioniert, unabhängig davon, ob Sie sich in einer Windows Forms-Anwendung, in einer WPF-Anwendung oder in einer von Ihnen entwickelten Multithread-Konsolenanwendung befinden. Sowohl Winforms als auch WPF stellen geeignete SynchronizationContexts für ihren Haupt- / UI-Thread bereit und installieren diese.

Das allgemeine Verfahren zum Aufrufen eines Delegaten in einem bestimmten Thread lautet wie folgt:

  • Sie müssen den SynchronizationContext des Zielthreads ( Z ) erfassen, damit Sie einen Delegaten an diesen Thread Send (synchron) oder Post (asynchron) erstellen können. Die Vorgehensweise besteht darin, den von SynchronizationContext.Current zurückgegebenen Synchronisationskontext zu speichern, während Sie sich im Zielthread Z befinden. (Dieser Synchronisationskontext muss zuvor im Thread Z registriert worden sein.) Speichern Sie diese Referenz dann an einem Ort, auf den der Thread A zugreifen kann.

  • Während Sie sich in Thread A befinden, können Sie den erfassten Synchronisationskontext verwenden, um einen beliebigen Delegaten an Thread Z zu senden oder zu senden: zSyncContext.Post(_ => { ... }, null);

Andere Tipps

Wenn Sie das Aufrufen eines Delegaten in einem Thread unterstützen möchten, der ansonsten keine Nachrichtenschleife hat, müssen Sie im Grunde eine eigene implementieren.

Eine Nachrichtenschleife ist nicht besonders magisch: Sie ähnelt einem Verbraucher in einem normalen Produzenten- / Konsumentenmuster. Es führt eine Warteschlange mit zu erledigenden Aufgaben (normalerweise Ereignisse, auf die reagiert werden muss) und durchläuft die entsprechende Warteschlange. Wenn nichts mehr zu tun ist, wartet es, bis etwas in die Warteschlange gestellt wird.

Anders ausgedrückt: Sie können sich einen Thread mit einer Nachrichtenschleife als einen Thread-Pool mit einem Thread vorstellen.

Sie können dies problemlos selbst implementieren, auch in einer Konsolen-App. Denken Sie daran, dass der Thread, wenn er sich um die Arbeitswarteschlange dreht, auch nichts anderes tun kann - während der Hauptausführungsthread in einer Konsolen-App normalerweise eine Abfolge von Aufgaben ausführen und dann beenden soll.

Wenn Sie .NET 4 verwenden, ist es sehr einfach, eine Produzenten- / Konsumentenwarteschlange mithilfe der rel zu implementieren="nofollow"> BlockingCollection class.

Ich bin kürzlich auf diesen Artikel gestoßen und habe festgestellt, dass er ein Lebensretter ist.Die Verwendung einer blockierenden, gleichzeitigen Warteschlange ist die geheime Sauce, auf die Jon Skeet oben hingewiesen hat.Das beste "How-to", das ich bei all dieser Arbeit gefunden habe, ist Dieser Artikel über CodeProject von Mike Peretz.Der Artikel ist Teil einer dreiteiligen Reihe zum SynchronizationContext, die Codebeispiele enthält, die leicht in Produktionscode umgewandelt werden können.Beachten Sie, dass Peretz nur alle Details ausfüllt, aber er erinnert uns auch daran, dass der Basissynchronisationskontext im Wesentlichen wertlose Implementierungen von Post () und Send () enthält und daher wirklich als abstrakte Basisklasse angesehen werden sollte.Ein gelegentlicher Benutzer der Basisklasse könnte überrascht sein, dass sie die Probleme der realen Welt nicht löst.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top