2017-09-13 1 views
1

Dies ist eine Frage, die ich in WPF.NET konfrontiert wurde. Um das Problem zu veranschaulichen, lassen Sie uns in der Klasse einen Blick unter: Warum tritt ein Deadlock bei der Verwendung von ManualResetEvent und der asynchronen Methode in WPF auf?

public class TaskRunnerWithProgressFeedback(){ 
    ManualResetEvent _event = new ManualResetEvent(false); 
    public void RunTask(Action action) { 
     _event.Reset(); 
     //Display loading screen 
     RunAsync(action); 
     Console.WriteLine("Load completed."); 
     //Hide loading screen 
     _event.WaitOne(); 
    } 

    private async void RunAsync(Action action) { 
     await Task.Run(() => action.Invoke()); 
     _event.Set(); 
    } 
} 

So habe ich diese Klasse hier, und ich werde die RunTask Methode von einem UI-Thread aufrufen. Zum Beispiel:

private void Button1_OnClick(object sender , RoutedEventArgs e) { 
    var x = new TaskRunnerWithProgressFeedback(); 
    x.RunTask(()=>{ /*Some time-consuming action*/ }); 
} 

Und wenn Button1 geklickt wird, läuft das ganze Programm in eine Deadlocks Situation. Haben Sie eine Erklärung für diese Situation?

Fußnote: Ich brauche die TaskRunnerWithProgressFeedback Klasse für mich, um Verhaltenstests durchzuführen. Ich verwende nicht BackgroundWorker, weil es diese Tests bricht.

+1

Wo ist 'WaitOne()'? – Sinatr

+1

Rufen Sie keine Async-Methode ohne "erwarten" an. Und verwenden Sie 'async void' Methoden nicht mit Ausnahme von Event-Handlern. – dymanoid

+0

Sinatr sorry, das war ein Unfall, nur hinzugefügt es zurück. –

Antwort

2

Der Aufruf von _event.Set() soll auf dem UI-Thread ausgeführt werden, sobald die Task, die Sie im RunAsync Methode erstellen abgeschlossen hat.

Der Deadlock tritt auf, wenn Sie im UI-Thread _event.WaitOne() aufrufen, bevor diese Aufgabe abgeschlossen ist.

Weil dann die UI-Threads warten auf die ManualResetEvent eingestellt werden, aber es wird nie werden, weil der Code, den die Set() Methode aufruft, kann nicht auf dem UI-Thread ausgeführt werden, da es durch den WaitOne() Anruf blockiert wird.

Dies ist im Grunde, wie async/await funktioniert. Die Methode async wird synchron ausgeführt, bis sie auf await trifft und dann zum Aufrufer zurückkehrt. Der Kontext (in diesem Fall der Dispatcher-Thread) wird erfasst, und der Rest der async-Methode wird dann auf demselben Dispatcher/UI-Thread ausgeführt, auf dem die Methode async aufgerufen wurde, nachdem die erwartete Methode abgeschlossen wurde.

Aber der Rest der Methode kann natürlich nicht ausgeführt werden, bis der Kontext Thread frei ist. Und in diesem Fall wird es nie sein, weil es auf den Rest der async Methode wartet, um Set() => Deadlock aufzurufen.

+0

Dies ist eine großartige Beschreibung des Problems. Um es zu beheben, können Sie 'AsyncManualResetEvent' aus [meiner AsyncEx-Bibliothek] verwenden (https://github.com/StephenCleary/AsyncEx) –

Verwandte Themen