Ich habe einige Best Practices für die asynchrone Programmierung mit den async
/await
-Schlüsselwörtern von c # gefunden (ich bin neu in c # 5.0).
Einer der Ratschläge war der folgende:
Stabilität: Kennen Sie Ihre Synchronisationskontexte
... Einige Synchronisierungskontexte sind nicht wiedereintrittsfähig und Singlethread. Dies bedeutet, dass jeweils nur eine Arbeitseinheit im Kontext ausgeführt werden kann. Ein Beispiel dafür ist der Windows-UI-Thread oder der ASP.NET-Anforderungskontext ..__ In diesen Kontexten für die Synchronisierung mit einem einzigen Thread können Sie sich leicht selbst blockieren. Wenn Sie eine Aufgabe aus einem Einzelthread-Kontext erzeugen und dann auf diese Aufgabe im Kontext warten, blockiert der wartende Code möglicherweise die Hintergrundaufgabe.
public ActionResult ActionAsync()
{
// DEADLOCK: this blocks on the async task
var data = GetDataAsync().Result;
return View(data);
}
private async Task<string> GetDataAsync()
{
// a very simple async method
var result = await MyWebService.GetDataAsync();
return result.ToString();
}
Wenn ich versuche, es selbst zu zerlegen, wird der Haupt-Thread in MyWebService.GetDataAsync();
zu einem neuen Thread, aber da der Haupt-Thread dort wartet, wartet er auf das Ergebnis in GetDataAsync().Result
. Inzwischen sagen wir, die Daten sind fertig. Warum setzt der Haupt-Thread nicht seine Fortführungslogik fort und gibt ein String-Ergebnis von GetDataAsync()
zurück?
Kann mir bitte jemand erklären, warum es im obigen Beispiel einen Deadlock gibt? Ich bin völlig ahnungslos, was das Problem ist ...
Schauen Sie sich ein Beispiel in hier an, Stephen hat eine klare Antwort für Sie:
Das passiert also, beginnend mit der Top-Level-Methode (Button1_Click für UI/MyController.Get für ASP.NET):
Die Methode der obersten Ebene ruft GetJsonAsync (im UI/ASP.NET-Kontext) auf.
GetJsonAsync startet die Anforderung REST durch Aufruf von HttpClient.GetStringAsync (immer noch im Kontext).
GetStringAsync gibt eine nicht abgeschlossene Task zurück, die angibt, dass die Anforderung REST nicht abgeschlossen ist.
GetJsonAsync erwartet die von GetStringAsync zurückgegebene Aufgabe. Der Kontext wird erfasst und verwendet, um die Ausführung von .__ fortzusetzen. GetJsonAsync-Methode später. GetJsonAsync gibt eine nicht abgeschlossene Aufgabe zurück Gibt an, dass die GetJsonAsync-Methode nicht abgeschlossen ist.
Die Methode der obersten Ebene blockiert synchron die von GetJsonAsync zurückgegebene Task. Dies blockiert den Kontext-Thread.
... Die Anforderung REST wird schließlich abgeschlossen. Damit ist die von GetStringAsync zurückgegebene Aufgabe abgeschlossen.
Die Fortsetzung für GetJsonAsync kann jetzt ausgeführt werden und wartet, bis der Kontext verfügbar ist, damit er im Kontext ausgeführt werden kann.
Sackgasse. Die Methode der obersten Ebene blockiert den Kontext-Thread und wartet, bis GetJsonAsync abgeschlossen ist. GetJsonAsync wartet auf Der Kontext muss frei sein, damit er abgeschlossen werden kann. Für das Beispiel der Benutzeroberfläche ist der "Kontext" ist der UI-Kontext; Für das ASP.NET-Beispiel lautet der "Kontext" den ASP.NET-Anforderungskontext. Diese Art von Deadlock kann für .__ verursacht werden. entweder "Kontext".
Ein weiterer Link, den Sie lesen sollten:
GetDataAsync().Result;
wird ausgeführt, wenn die von GetDataAsync()
zurückgegebene Aufgabe abgeschlossen ist. In der Zwischenzeit blockiert sie den UI-Threadreturn result.ToString()
) wird zur Ausführung in den UI-Thread aufgenommenGetDataAsync()
zurückgegebene Aufgabe wird abgeschlossen, wenn die Fortsetzung in der Warteschlange ausgeführt wirdDer Deadlock kann durch bereitgestellte Alternativen aufgehoben werden, um Fact 1 oder Fact 2 zu vermeiden.
var data = await GetDataAsync()
, wodurch der UI-Thread weiter ausgeführt werden kannvar data = Task.Run(GetDataAsync).Result
, um die Fortsetzung im Sync-Kontext eines Threadpool-Threads zu buchen. Dadurch kann die von GetDataAsync()
zurückgegebene Aufgabe abgeschlossen werden. Dies wird in einem Artikel von Stephen Toub sehr gut erklärt, etwa in der Mitte, wo er das Beispiel von DelayAsync()
verwendet.
Ich habe gerade wieder in einem MVC.Net-Projekt mit diesem Problem herumgepfuscht. Wenn Sie async-Methoden aus einer PartialView aufrufen möchten, dürfen Sie die PartialView nicht asynchron machen. Sie erhalten eine Ausnahme, wenn Sie dies tun.
Im Grunde eine einfache Problemumgehung in dem Szenario, in dem Sie eine async-Methode von einer sync-Methode aufrufen möchten, können Sie Folgendes tun:
Beispiel:
public ActionResult DisplayUserInfo(string userName)
{
// trick to prevent deadlocks of calling async method
// and waiting for on a sync UI thread.
var syncContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null);
// this is the async call, wait for the result (!)
var model = _asyncService.GetUserInfo(Username).Result;
// restore the context
SynchronizationContext.SetSynchronizationContext(syncContext);
return PartialView("_UserInfo", model);
}
Ein weiterer wichtiger Punkt ist, dass Sie Aufgaben nicht blockieren und async ganz nach unten verwenden sollten, um Deadlocks zu vermeiden. Dann wird alles asynchron und nicht synchron blockiert.
public async Task<ActionResult> ActionAsync()
{
var data = await GetDataAsync();
return View(data);
}
private async Task<string> GetDataAsync()
{
// a very simple async method
var result = await MyWebService.GetDataAsync();
return result.ToString();
}
Eine Problemumgehung war, eine Join
-Erweiterungsmethode für die Aufgabe zu verwenden, bevor nach dem Ergebnis gefragt wird.
Der Code sieht so aus:
public ActionResult ActionAsync()
{
var task = GetDataAsync();
task.Join();
var data = task.Result;
return View(data);
}
Wo ist die Join-Methode:
public static class TaskExtensions
{
public static void Join(this Task task)
{
var currentDispatcher = Dispatcher.CurrentDispatcher;
while (!task.IsCompleted)
{
// Make the dispatcher allow this thread to work on other things
currentDispatcher.Invoke(delegate { }, DispatcherPriority.SystemIdle);
}
}
}
Ich bin nicht genug in der Domäne, um die Nachteile dieser Lösung zu sehen (falls vorhanden)