Ich versuche zu verstehen, wie Async in der einfachsten Form wartet. Ich möchte für dieses Beispiel eine sehr einfache Methode erstellen, die zwei Zahlen hinzufügt, vorausgesetzt, es ist überhaupt keine Verarbeitungszeit, es geht nur darum, hier ein Beispiel zu formulieren.
private async Task DoWork1Async()
{
int result = 1 + 2;
}
private async Task DoWork2Async()
{
Task.Run( () =>
{
int result = 1 + 2;
});
}
Wenn ich auf DoWork1Async()
warte, wird der Code synchron oder asynchron ausgeführt?
Muss ich den Synchronisierungscode mit Task.Run
umbrechen, um die Methode wartbar UND asynchron zu machen, damit der UI-Thread nicht blockiert wird?
Ich versuche herauszufinden, ob meine Methode Task
ist oder Task<T>
zurückgibt. Muss ich den Code mit Task.Run
umbrechen, um ihn asynchron zu machen?.
Dumme Frage, da bin ich mir sicher, aber ich sehe Beispiele im Netz, in denen Leute auf Code warten, der nichts Asynchrones enthält und nicht in einen Task.Run
oder StartNew
eingeschlossen ist.
Lassen Sie uns zunächst einige Begriffe klären: "Asynchron" (async
) bedeutet, dass der aufrufende Thread möglicherweise die Kontrolle erhält, bevor er gestartet wird. In einer async
Methode sind diese "Ertragspunkte" await
Ausdrücke.
Dies unterscheidet sich stark von dem Begriff "asynchron", wie er in der MSDN-Dokumentation jahrelang als "auf einem Hintergrund-Thread ausgeführt" (falsch) verwendet wird.
Um das Problem weiter zu verwirren, ist async
ganz anders als "erwartbar"; Es gibt einige async
Methoden, deren Rückgabetypen nicht erwartet werden können, und viele Methoden, die erwartete Typen zurückgeben, die nicht async
sind.
Genug davon, was sie nicht sind ; Das sind sie :
async
ermöglicht eine asynchrone Methode (dh, es ermöglicht await
Ausdrücke). async
Methoden können Task
, Task<T>
oder (falls erforderlich) void
zurückgeben.Task
und Task<T>
.Wenn wir also Ihre Frage in "Wie kann ich eine Operation auf einem Hintergrund-Thread so ausführen, dass es absehbar ist" umformulieren, lautet die Antwort Task.Run
:
private Task<int> DoWorkAsync() // No async because the method does not need await
{
return Task.Run(() =>
{
return 1 + 2;
});
}
(Aber dieses Muster ist ein schlechter Ansatz; siehe unten).
Lautet Ihre Frage jedoch "Wie erstelle ich eine async
-Methode, die dem Aufrufer zurückgegeben werden kann, anstatt ihn zu blockieren", deklarieren Sie die async
-Methode und verwenden Sie await
"Nachgebende" Punkte:
private async Task<int> GetWebPageHtmlSizeAsync()
{
var client = new HttpClient();
var html = await client.GetAsync("http://www.example.com/");
return html.Length;
}
Das Grundmuster der Dinge ist also, dass async
Code von "awaitables" in seinen await
Ausdrücken abhängt. Diese "erwarteten Ergebnisse" können andere async
Methoden oder nur reguläre Methoden sein, die erwartete Ergebnisse zurückgeben. Reguläre Methoden, die Task
/Task<T>
zurückgeben, können Task.Run
verwenden, um Code in einem Hintergrund-Thread auszuführen, oder (häufiger) TaskCompletionSource<T>
oder eine seiner Verknüpfungen (TaskFactory.FromAsync
, Task.FromResult
, etc). Ich empfehle nicht , eine ganze Methode in Task.Run
zu schreiben; synchrone Methoden sollten synchrone Signaturen haben und es sollte dem Konsumenten überlassen bleiben, ob sie in einen Task.Run
eingeschlossen werden sollen:
private int DoWork()
{
return 1 + 2;
}
private void MoreSynchronousProcessing()
{
// Execute it directly (synchronously), since we are also a synchronous method.
var result = DoWork();
...
}
private async Task DoVariousThingsFromTheUIThreadAsync()
{
// I have a bunch of async work to do, and I am executed on the UI thread.
var result = await Task.Run(() => DoWork());
...
}
Ich habe ein async
/await
Intro in meinem Blog; Am Ende stehen einige gute Nachverfolgungsressourcen. Die MSDN-Dokumente für async
sind auch ungewöhnlich gut.
Eines der wichtigsten Dinge, an die Sie denken müssen, wenn Sie eine Methode mit async dekorieren, ist, dass es zumindest eine gibt eins warten Operator innerhalb der Methode. In Ihrem Beispiel würde ich es wie unten gezeigt mit TaskCompletionSource übersetzen.
private Task<int> DoWorkAsync()
{
//create a task completion source
//the type of the result value must be the same
//as the type in the returning Task
TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
Task.Run(() =>
{
int result = 1 + 2;
//set the result to TaskCompletionSource
tcs.SetResult(result);
});
//return the Task
return tcs.Task;
}
private async void DoWork()
{
int result = await DoWorkAsync();
}
Wenn Sie eine Methode mit Task.Run ausführen, ruft Task einen Thread aus dem Threadpool ab, um diese Methode auszuführen. Aus der Sicht des UI-Threads ist es "asynchron", da es den UI-Thread nicht blockiert. Dies ist in Ordnung für Desktop-Anwendungen, da Sie normalerweise nicht viele Threads benötigen, um Benutzerinteraktionen zu erledigen.
Für Webanwendungen wird jede Anforderung jedoch von einem Thread-Pool-Thread bearbeitet, und daher kann die Anzahl der aktiven Anforderungen durch Speichern solcher Threads erhöht werden. Die häufige Verwendung von Threadpool-Threads zur Simulation eines asynchronen Vorgangs ist für Webanwendungen nicht skalierbar.
True Async erfordert nicht unbedingt die Verwendung eines Threads für E/A-Vorgänge wie Datei-/Datenbankzugriff usw. Sie können dies lesen, um zu verstehen, warum für E/A-Vorgänge keine Threads erforderlich sind. http://blog.stephencleary.com/2013/11/there-is-no-thread.html
In Ihrem einfachen Beispiel handelt es sich um eine reine CPU-gebundene Berechnung, daher ist die Verwendung von Task.Run in Ordnung.