2012-11-01 3 views
5

Ich habe eine Task, die ich beginne und möchte auf den Abschluss in einer WPF App warten. In dieser Aufgabe rufe ich eine Action auf dem Dispatcher auf.Task.Wait vs Task.RunSyncronous wo Aufgabe hat WPF Dispatcher.Invoke

Wenn ich Task.Wait() verwende, scheint es zu hängen, als ob die Methode nie beendet wurde. Breakpoints innerhalb des Dispatcher.Invoke werden auch nie getroffen.

Wenn ich Task.RunSyncronously() verwende, scheint es richtig zu funktionieren und Breakpoints innerhalb des Dispatcher werden getroffen.

Warum gibt es einen Unterschied?

Codebeispiel unten:

public void ExampleMethod() 
{ 
    // When doing the following: 
    var task = new Task(LoadStuff); 

    // This never returns: 
    task.Start(); 
    task.Wait(); 

    // This version, however, does: 
    task.RunSyncronously(); 
} 

private void LoadStuff() 
{ 
    ObservableCollection<StuffObj> stuff = Stuff.Load(arg1, true); 

    DispatchHelper.RunOnDispatcher(() => 
    { 
     ... 
    }); 
} 

public static class DispatchHelper 
{ 
    public static void RunOnDispatcher(Action action) 
    { 
     Application.Current.Dispatcher.Invoke(action); 
    } 
}  

Antwort

5

Ja, es gibt einen großen Unterschied. Wenn Sie RunSyncronously verwenden, führen Sie die Aufgabe einfach im UI-Thread aus. Wenn Sie es in einem Hintergrundthread und uns Wait starten, wird der Code in einem Hintergrundthread ausgeführt und der UI-Thread ist blockiert. Wenn der Code innerhalb dieser Task den UI-Thread aufruft und der UI-Thread blockiert wird (durch die Wait), haben Sie einen Deadlock erstellt und die Anwendung bleibt eingefroren.

Beachten Sie, dass wenn Sie RunSyncronously für diese Aufgabe aus einem Nicht-UI-Thread verwendet und der UI-Thread von etwas anderem blockiert wurde, Sie immer noch den Deadlock sehen würden.

Jetzt, da für das, was Sie tun sollen, gibt es wirklich zwei Möglichkeiten:

  1. Die Aufgabe selbst dauert nicht wirklich eine lange Zeit, und es eher im UI-Thread ausgeführt werden soll wirklich als in einem Hintergrundthread. Der UI-Thread wird nicht lange genug (vorübergehend) eingefroren, um ein Problem zu sein, wenn all diese Arbeiten direkt in der Benutzeroberfläche ausgeführt werden. Wenn dies der Fall ist, sollten Sie es wahrscheinlich nicht einmal zu einer Aufgabe machen, einfach den Code in eine Methode einfügen und die Methode aufrufen.

  2. Die Ausführung der Aufgabe dauert sehr lange, und nach dieser Arbeit wird die Benutzeroberfläche aktualisiert. Wenn das der Fall ist, ist es wichtig, dass es nicht RunSyncronously ist, sondern in einem Hintergrundthread gestartet wird. Um zu verhindern, dass Ihre gesamte Anwendung blockiert, bedeutet dies, dass Sie nicht den UI-Thread durch einen Wait Anruf blockieren müssen. Was Sie tun müssen, wenn Sie Code haben, den Sie nach Abschluss der Aufgabe ausführen möchten, besteht darin, der Aufgabe eine Fortsetzung hinzuzufügen. In C# 4.0 könnte dies getan werden, indem ContinueWith für die Aufgabe aufgerufen und ein Delegat hinzugefügt wird, um ausgeführt zu werden. In C# 5.0 + können Sie stattdessen await auf die entsprechende Aufgabe (anstatt Wait ing, es ist eigentlich ein großer Unterschied) und es wird automatisch den Rest der Methode als Fortsetzung für Sie (in der Tat ist es syntaktischer Zucker für eine explizite ContinueWith Anruf, aber es ist ein sehr nützliches).

+0

Meinst du nicht .NET 4.5, es gibt noch kein .NET 5. Außerdem generieren asynchrone Methoden eine Statusmaschine statt ContinueWith. –

+0

@BrianReichle Ich meinte C#, nicht .NET, aber ja, es war falsch. Soweit "async", ja, erzeugt es eine Zustandsmaschine, und diese Zustandsmaschine fügt sich selbst als eine Fortsetzung der Aufgabe hinzu, die erwartet wird. Der Zustandsautomat ist der Mechanismus, mit dem er weiß, wo er beim Fortsetzen der Fortsetzung aufhört, aber das Hinzufügen einer Fortsetzung zu der Aufgabe, die erwartet wird, ist, wie alles läuft, wenn die Aufgabe beendet ist. – Servy

+0

Die Zustandsmaschine plant über den von 'GetAwaiter()' zurückgegebenen Warter, nicht 'ContinueWith' (dies erlaubt uns, andere Dinge als Aufgaben abzuwarten). Sie rufen beide zu einer gemeinsamen Implementierung auf, aber "task.GetAwaiter(). OnCompleted (...)' ruft nicht "ContinueWith" auf (insbesondere vermeidet ersteres das Erstellen einer neuen Aufgabe). Aber ich nehme an, das wird nur pedantisch, die meisten Leute kümmern sich nicht um die Implementierungsdetails :) –

Verwandte Themen