2017-03-22 1 views
1

Ich habe mehrere asynchrone Methoden, die zurück zum Haupt-ui-Thread synchronisiert werden müssen.Wie blockiere ich eine Methode, während eine asynchrone Methode ausgeführt wird, ohne dass es zu Deadlocks im UI-Thread kommt

async Task MyAsyncMethod() { 
    await DoSomeThingAsync().ConfigureAwait(true); 
    DoSomethingInTheGui(); 
} 

jetzt brauche ich sie von einem syncronous Ereignisbehandlungsroutine aufzurufen, die von der GUI-Thread ausgelöst wird, und der Event-Handler kann nicht abgeschlossen werden, bis die Asynchron-Methode durchgeführt wird. So ist MyAsyncMethod().Wait() keine Option, auch keine Art von Feuer-und-Vergessen-Lösung.

Dieser Ansatz Nito.AsyncEx vielversprechend schien, aber es immer noch Deadlocks: https://stackoverflow.com/a/9343733/249456

Die einzige Lösung, die ich mir scheint wie ein Hack gefunden habe:

public void RunInGui(Func<Task> action) 
{ 
    var window = new Window(); 
    window.Loaded += (sender, args) => action() 
     .ContinueWith(p => { 
      window.Dispatcher.Invoke(() => 
      { 
       window.Close(); 
      }); 
     }); 
     window.ShowDialog(); 
} 

Gibt es einen Weg, um der gleiche Effekt (Blockieren Sie die aufrufende Methode, erlauben Sie die Synchronisierung auf den GUI-Thread), ohne ein neues Fenster zu erstellen?

Hinweis: Mir ist bewusst, dass Refactoring wahrscheinlich die beste Option wäre, aber das ist eine massive Aufgabe, die wir über längere Zeit erledigen müssen. Erwähnenswert ist auch, dass dies ein Plugin für Autodesk Inventor ist. Die API hat mehrere Macken, und alle API-Aufrufe (auch nicht-ui-bezogen) müssen vom Haupt/ui-Thread ausgeführt werden.

Auch erwähnenswert ist, dass wir einen Verweis auf den Hauptthreads Dispatcher behalten und MainThreadDispatcher.InvokeAsync(() => ...) rund um die Codebasis verwenden.

+4

Sie müssen Ihren UIhread nicht blockieren. Solange Sie eine Anforderung haben, Ihren UInthread zu blockieren (insbesondere während andere Prozesse tatsächlich den UInthread verwenden müssen), befinden Sie sich in einer Welt voller Verletzungen. Entfernen Sie diese Anforderung, wenn Sie Wert auf Ihre Gesundheit legen. – Servy

+0

* Warum * einen synchronen Event-Handler verwenden? Warum nicht ein * asynchroner & Event-Handler? ZB async void Button_Click (..) {awary MyAsyncMethod1(); away MyAsyncMethod2();} ' –

+0

Eine weitere Option ist die Verwendung von Progress , um Nachrichten zurück an den UI-Thread zu senden. Die Nutzlast eines Progress muss keine Zeichenfolge oder eine Fortschrittsnachricht sein, es kann sich um ein beliebiges Objekt handeln. Aktivieren Sie [Async in 4.5: Aktivieren von Fortschritt und Abbruch in Async-APIs] (https://blogs.msdn.microsoft.com/dotnet/2012/06/06/asyncin-4-5-enabling-progress-and-cancellation -in-async-apis /) –

Antwort

7

Die einzige Lösung, die ich gefunden habe, scheint wie ein Hack mir

Alle Sync-over-Asynchron-Lösungen Hacks sind. Ich zähle die häufigsten in meinem article on brownfield async auf. Es ist wichtig zu beachten, dass keine Lösung für beliebigen Code existiert. Jede Lösung entweder Deadlocks oder wirft in einer Situation.

Eine der leichteren Hacks ist auf einem Thread-Pool-Thread zu blockieren:

Task.Run(() => action()).Wait(); 

Allerdings, wenn Ihr action() einen UI Kontext erfordert, dann die nicht funktionieren.

Der nächste Schritt besteht darin, einen Single-Thread-Kontext zu verwenden. Dies ist der Ansatz von meiner AsyncContext Art genommen:

AsyncContext.Run(() => action()); 

Dies wäre eine Sackgasse, wenn action für den UI-Thread Nachrichtenwarteschlange wartet gepumpt werden. Da dieser aber Window Deadlocks funktioniert, scheint dies der Fall zu sein.

Wenn Sie weiter nach unten gehen, können Sie nested pumping verwenden. Bitte beachten Sie, dass nested pumping opens up a whole world of hurt due to unexpected reentrancy. Es gibt Code auf GitHub, der how to do nested pumping on WPF zeigt. Jedoch empfehle ich in hohem Grade, dass Sie Ihren Code refaktorisieren, anstatt Reentrancy zu umarmen.

+0

Danke für die schnelle Antwort. Refactoring ist meine bevorzugte Lösung, aber das ist ein gewaltiges Unterfangen, also muss es schrittweise getan werden. Wie kann ich feststellen, wo mein Code für die zu pumpende Nachrichtenwarteschlange der UI-Threads steht? – Henrik

+0

@Henrik: Sie könnten 'AsyncContext' verwenden und dann, wenn es sich um Deadlocks handelt, den Aufruf-Stack Ihres UI-Threads ansehen. –

Verwandte Themen