Vorwort : Ich suche eine Erklärung, nicht nur eine Lösung. Ich kenne die Lösung bereits.
Obwohl ich mehrere Tage damit verbracht habe, MSDN-Artikel über das aufgabenbasierte asynchrone Muster (TAP) zu studieren, asynchron zu sein und zu warten, bin ich immer noch etwas verwirrt über einige der feineren Details.
Ich schreibe einen Logger für Windows Store Apps und möchte sowohl die asynchrone als auch die synchrone Protokollierung unterstützen. Die asynchronen Methoden folgen dem TAP, die synchronen sollten all dies verbergen und wie normale Methoden aussehen und funktionieren.
Dies ist die Kernmethode der asynchronen Protokollierung:
private async Task WriteToLogAsync(string text)
{
StorageFolder folder = ApplicationData.Current.LocalFolder;
StorageFile file = await folder.CreateFileAsync("log.log",
CreationCollisionOption.OpenIfExists);
await FileIO.AppendTextAsync(file, text,
Windows.Storage.Streams.UnicodeEncoding.Utf8);
}
Nun die entsprechende synchrone Methode ...
Version 1 :
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.Wait();
}
Das sieht richtig aus, funktioniert aber nicht. Das ganze Programm friert für immer ein.
Version 2 :
Hmm .. Vielleicht wurde die Aufgabe nicht gestartet?
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.Start();
task.Wait();
}
Das wirft InvalidOperationException: Start may not be called on a promise-style task.
Version 3:
Hmm .. Task.RunSynchronously
klingt vielversprechend.
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.RunSynchronously();
}
Das wirft InvalidOperationException: RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.
Version 4 (die Lösung):
private void WriteToLog(string text)
{
var task = Task.Run(async () => { await WriteToLogAsync(text); });
task.Wait();
}
Das funktioniert. 2 und 3 sind also die falschen Werkzeuge. Aber 1? Was ist los mit 1 und was ist der Unterschied zu 4? Was bringt 1 zum Einfrieren? Gibt es ein Problem mit dem Aufgabenobjekt? Gibt es einen nicht offensichtlichen Stillstand?
quelle
Antworten:
In
await
Ihrer asynchronen Methode wird versucht, zum UI-Thread zurückzukehren.Da der UI-Thread damit beschäftigt ist, auf den Abschluss der gesamten Aufgabe zu warten, liegt ein Deadlock vor.
Durch Verschieben des asynchronen Aufrufs wird
Task.Run()
das Problem behoben .Da der asynchrone Aufruf jetzt in einem Thread-Pool-Thread ausgeführt wird, wird nicht versucht, zum UI-Thread zurückzukehren, und daher funktioniert alles.
Alternativ können Sie aufrufen,
StartAsTask().ConfigureAwait(false)
bevor Sie auf die innere Operation warten, damit sie zum Thread-Pool und nicht zum UI-Thread zurückkehrt, wodurch der Deadlock vollständig vermieden wird.quelle
ConfigureAwait(false)
ist die geeignete Lösung in diesem Fall. Da die Rückrufe im erfassten Kontext nicht aufgerufen werden müssen, sollte dies nicht der Fall sein. Als API-Methode sollte sie intern behandelt werden, anstatt alle Aufrufer zu zwingen, den UI-Kontext zu verlassen.Microsoft.Bcl.Async
.Das Aufrufen von
async
Code aus synchronem Code kann sehr schwierig sein.Die vollständigen Gründe für diesen Deadlock erkläre ich in meinem Blog . Kurz gesagt, es gibt einen "Kontext", der standardmäßig am Anfang jedes Kontextes gespeichert
await
und zum Fortsetzen der Methode verwendet wird.Wenn dies in einem UI-Kontext aufgerufen wird
await
,async
versucht die Methode nach Abschluss des Vorgangs, diesen Kontext erneut einzugeben, um die Ausführung fortzusetzen. Leider blockiert Code mitWait
(oderResult
) einen Thread in diesem Kontext, sodass dieasync
Methode nicht abgeschlossen werden kann.Die Richtlinien, um dies zu vermeiden, sind:
ConfigureAwait(continueOnCapturedContext: false)
so viel wie möglich. Auf diese Weise können Ihreasync
Methoden weiter ausgeführt werden, ohne den Kontext erneut eingeben zu müssen.async
ganzen Weg. Verwenden Sieawait
anstelle vonResult
oderWait
.Wenn Ihre Methode von Natur aus asynchron ist, sollten Sie (wahrscheinlich) keinen synchronen Wrapper verfügbar machen .
quelle
async
wie ich dies tun und ein Feuer verhindern und eine Situation vergessen würde.await
wird abcatch
VS2015 blockweise unterstützt . Wenn Sie eine ältere Version verwenden, können Sie die Ausnahme einer lokalen Variablen zuweisen undawait
den Block nach dem Catch ausführen .Hier ist was ich getan habe
funktioniert super und blockiert nicht den UI-Thread
quelle
In einem kleinen benutzerdefinierten Synchronisationskontext kann die Synchronisierungsfunktion auf den Abschluss der Asynchronisierungsfunktion warten, ohne einen Deadlock zu verursachen. Hier ist ein kleines Beispiel für die WinForms-App.
quelle