2 Stimmen

C# Nicht-UI-übergreifender Thread-Aufruf

Ich versuche, Cross-Thread-Aufrufe in C# zu machen.

Wenn ich die Methoden eines Objekts, das im Kontext von Thread A erstellt wurde, von einer statischen Methode aus aufrufe, die von Thread B aus aufgerufen wurde, läuft die Methode immer in Thread B. Das möchte ich nicht, ich möchte, dass sie auf demselben Thread läuft wie das Objekt von Thread A, dessen Methoden ich aufrufe.

Invoke funktioniert gut für UI-Aufrufe, und ich habe Dutzende von Artikeln und SO-Antworten in Bezug auf verschiedene Möglichkeiten, Cross-Threaded Forms/WPF-Aufrufe zu machen gelesen. Doch was auch immer ich versuche (Ereignisbehandlung, Delegaten, etc.) Thread A's Objekt Methode wird immer in Thread B ausgeführt, wenn es von Thread B aufgerufen wird.

In welchem Teil der Bibliothek sollte ich suchen, um dieses Problem zu lösen? Wenn es relevant ist, Thread B derzeit "spinnt", liest von einem Netzwerkanschluss und gelegentlich ruft Thread A's Objekt Methode durch einen Delegaten, der in Thread A erstellt wurde und in mit einem ParameterizedThreadStart übergeben.

Ich möchte nicht das Paradigma wechseln, sondern lediglich eine Nachricht (eine Aufforderung zum Aufrufen einer Methode) von einem Thread (Thread B) an einen anderen (Thread A) senden.

EDIT:

Meine Frage war: "In welchem Teil der Bibliothek sollte ich suchen, um dieses Problem zu lösen? Die Antwort scheint keine zu sein. Wenn ich den Verbrauch und die Abfrage klar abgrenzen will, muss ich meinen eigenen Code schreiben, um das zu erledigen.

6voto

cdhowie Punkte 142402

Immer wenn ich die Methoden eines Objekts aufrufe, das auf Thread A

Objekte werden nicht in Threads ausgeführt.

Damit dies funktioniert, müssen Sie eine Art Warteschlange erstellen, in die Sie einen Delegaten schieben können, der routinemäßig in der Hauptschleife von Thread A überprüft wird. Etwas wie dies, vorausgesetzt, dass Something.MainThreadLoop ist der Einstiegspunkt für Thread A:

public class Something
{
    private Queue<Action> actionQueue = new Queue<Action>();

    private volatile bool threadRunning = true;

    public void RunOnThread(Action action)
    {
        if (action == null)
            throw new ArgumentNullException("action");

        lock (actionQueue)
            actionQueue.Enqueue(action);
    }

    public void Stop()
    {
        threadRunning = false;
    }

    private void RunPendingActions()
    {
        while (actionQueue.Count > 0) {
            Action action;

            lock (actionQueue)
                action = actionQueue.Dequeue();

            action();
        }
    }

    public void MainThreadLoop()
    {
        while (threadRunning) {
            // Do the stuff you were already doing on this thread.

            // Then, periodically...
            RunPendingActions();
        }
    }
}

Mit einem Verweis auf ein "Something"-Objekt könnten Sie dann Folgendes tun:

something.RunOnThread(() => Console.WriteLine("I was printed from thread A!"));

3voto

Josh Smeaton Punkte 45913

Der Code läuft in Threads. Objekte sind (im Allgemeinen - siehe thread local) nicht an einen bestimmten Thread gebunden. Wenn Sie WinFormControl.Invoke oder WPFControl.Invoke ausführen, senden Sie eine Nachricht an die Message Pump bzw. den Dispatcher, um Code zu einem späteren Zeitpunkt auszuführen.

Die Nachrichtenpumpe sieht etwa so aus:

Message message;
while(GetMessage(&message))
{
    ProcessMessage(message);
}

Microsoft hat seine UI-Steuerelemente und Projekte speziell so gestaltet, dass sie das Posten von Nachrichten in verschiedenen Threads ermöglichen. Der Aufruf einer Methode von Thread A aus führt diese Methode immer auf Thread A aus, auch wenn sie am Ende eine Art asynchrone Arbeit ausführt und vorzeitig zurückkehrt.

Bearbeiten:

Was Sie meiner Meinung nach brauchen, ist das Producer-Consumer-Muster.

http://msdn.microsoft.com/en-us/library/yy12yx1f(VS.80).aspx

Vergessen Sie, die Nachrichten von Ihrem Haupt-Thread zu konsumieren, denn das ist es, was Sie anscheinend tun wollen. Konsumieren Sie von Thread C.

Thema A ist mit "viel wichtigeren Dingen" beschäftigt. Thread B dreht sich und wartet auf Nachrichten. Thread C konsumiert diese Nachrichten.

Es besteht keine Notwendigkeit für die Zusammenlegung von Threads.

1voto

CodingGorilla Punkte 19206

Sie müssen eine Art Warteschlange erstellen und Thread A diese Warteschlange auf Arbeit überwachen lassen. Wenn Thread A sieht, dass neue Arbeit in die Warteschlange kommt, kann er sie aus der Warteschlange nehmen und die Arbeit erledigen, um dann wieder auf weitere Arbeit zu warten.

Hier ist ein Pseudocode:

    public class ThreadAQueue
    {
        private Queue<delegate> _queue;
        private bool _quitWorking;

        public void EnqueueSomeWork(delegate work)
        {
            lock(_queue) 
            {
                _queue.Enqueue(work);
            }
        }

        private void DoTheWork()
        {
            while(!quitWorking) 
            {
                delegate myWork;

                lock(_queue)
                {
                    if(_queue.Count > 1)
                        myWork = _queue.Dequeue();
                }

                myWork();
            }
        }
    }

1voto

Dr. Wily's Apprentice Punkte 9952

EDIT: Ich denke, Sie wollen wahrscheinlich die Klasse System.Threading.AutoResetEvent verwenden. In der MSDN-Dokumentation finden Sie ein anständiges Beispiel für einen Thread, der auf einen anderen wartet, das meiner Meinung nach dem ähnelt, was Sie zu tun versuchen: http://msdn.microsoft.com/en-us/library/system.threading.autoresetevent.aspx Achten Sie insbesondere auf die Aufrufe von trigger.WaitOne() und trigger.Set()

EDIT2: Option #3 hinzugefügt, nachdem ich den neuen Kommentar von OP gelesen habe.

"Wann immer ich die Methoden eines Objekts aufrufe, das auf Thread A läuft ..." - Ein Objekt "läuft" nicht auf einem Thread und ist nicht wirklich Eigentum eines Threads, unabhängig davon, welcher Thread das Objekt erstellt hat.

Da sich Ihre Frage auf "Nicht-UI-Cross-Thread-Aufrufe" bezieht, gehe ich davon aus, dass Sie bereits mit "UI-Cross-Thread-Aufrufen" vertraut sind. Ich kann mir vorstellen, dass WinForms Ihnen den Eindruck vermittelt, dass ein Thread ein Objekt besitzt und dass Sie eine Nachricht an einen Thread senden müssen, damit dieser etwas tut.

WinForm-Steuerungsobjekte sind insofern ein Sonderfall, als sie einfach nicht richtig funktionieren, wenn Sie mit ihnen über einen Thread interagieren, der nicht derjenige ist, der sie erstellt hat, aber das liegt nicht an der Art und Weise, wie Threads und Objekte interagieren.

Nun aber zu Ihrer Frage.

Zunächst eine Frage zur Klärung des Problems: Sie haben erwähnt, was Thread B tut, aber was tut Thread A, bevor er von Thread B "aufgerufen" wird?

Hier sind ein paar Ideen, die meiner Meinung nach dem entsprechen, was Sie tun möchten:

  1. Legen Sie Thread A erst an, wenn Sie ihn brauchen. Anstatt Thread B zu veranlassen, "eine Nachricht an Thread A zu senden", sollten Sie lieber Thread B veranlassen, Thread A zu erstellen (oder ihn Thread C nennen, wenn Sie dies bevorzugen) und ihn zu diesem Zeitpunkt mit der Ausführung beginnen lassen.

  2. Wenn Sie benötigen, dass Thread A bereits existiert und Sie möchten, dass Thread A die Ereignisse von Thread B nur einzeln bearbeitet, können Sie Thread A warten lassen, bis er eine Benachrichtigung von Thread B erhält. die Klasse System.Threading.WaitHandle (abgeleitete Klassen von Interesse sind ManualResetEvent und AutoResetEvent).

    Thread A wird irgendwann WaitHandle.WaitOne() aufrufen, was ihn dazu veranlasst, eine Pause einzulegen und zu warten, bis Thread B WaitHandle.Set() für dasselbe WaitHandle-Objekt aufruft.

  3. Wenn Thread A mit anderen Dingen beschäftigt ist, sollten Sie vielleicht eine Art Flag-Variable einrichten. Ähnlich wie das WaitHandle-Konzept in Nr. 2, aber anstatt Thread A zum Anhalten zu veranlassen, soll Thread B einfach ein Flag setzen (vielleicht nur eine boolesche Variable), das Thread A signalisiert, dass er etwas tun muss. Während Thread A mit anderen Dingen beschäftigt ist, kann er dieses Flag regelmäßig überprüfen, um zu entscheiden, ob etwas zu tun ist oder nicht.

Erfordert die Methode, die Thread A auf Ihrem Objekt ausführt, irgendeine Eingabe von Thread B? Dann lassen Sie Thread B, bevor er WaitHandle.Set() aufruft, einige Daten in eine Warteschlange oder ähnliches stellen. Wenn dann Thread A "aktiviert" wird, kann er diese Daten aus der Warteschlange abrufen und mit der Ausführung der Objektmethode unter Verwendung dieser Daten fortfahren. Verwenden Sie einen Sperrmechanismus (d.h. die C#-Sperranweisung), um den Zugriff auf die Warteschlange zu synchronisieren.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X