8

Es gibt eine Task-Variable und lässt sagen, dass die Task gerade läuft .. indem Sie die folgende Zeile ausführen.Was passiert, wenn ich auf eine Aufgabe warte, die bereits ausgeführt wird oder ausgeführt wird?

await _task; 

Ich frage mich, was passiert, wenn ich diesen Code schreiben:

await _task; 
await _task; 

wäre es die Aufgabe zweimal ausführen? Oder eine Ausnahme auslösen, weil sie bereits ausgeführt wurde?

+4

Warum versuchst du es nicht? Es ist nicht schwer zu testen. – Enigmativity

+0

Die wichtigste Erkenntnis hier ist, dass * erwarten keine Aufgabe * ausgeführt wird. Warten * wartet asynchron auf die Beendigung einer Aufgabe *. Daher der Name "erwarten". Es gibt einen hartnäckigen Mythos, der auf eine Aufgabe wartet, aber natürlich nicht; Sie haben bereits eine begonnene Aufgabe in der Hand, wenn Sie darauf warten. –

+0

Der wahre Augenöffner für mich war das Konzept der "Fortsetzungen". Ich denke, ohne diese Einsicht ist es schwierig, "erwarten" zu begreifen. – Sentinel

Antwort

9

würde es die Aufgabe zweimal ausführen? Oder eine Exception werfen, weil bereits ausgeführt wird?

Nein und nein. Das einzige, was await tut, ist Task.GetAwaiter aufrufen, es verursacht nichts zu laufen. Wenn die Task bereits vollständig ausgeführt wurde, wird entweder der Wert extrahiert, wenn es sich um eine Task<T> handelt, oder synchron zur nächsten Zeile ausgeführt, wenn es sich um eine Task handelt, da es eine Optimierung für bereits abgeschlossene Tasks gibt.

Eine einfache Demo:

async Task Main() 
{ 
    var foo = FooAsync(); 
    await foo; 
    await foo; 

    var bar = BarAsync(); 
    var firstResult = await bar; 
    var secondResult = await bar; 

    Console.WriteLine(firstResult); 
    Console.WriteLine(secondResult); 
} 

public async Task FooAsync() 
{ 
    await Task.Delay(1); 
} 

public async Task<int> BarAsync() 
{ 
    await Task.Delay(1); 
    return 1; 
} 

Wenn Sie an die Zustandsmaschine selbst Drill-Down, werden Sie sehen:

this.<foo>5__1 = this.<>4__this.FooAsync(); 
taskAwaiter = this.<foo>5__1.GetAwaiter(); 
if (!taskAwaiter.IsCompleted) 
{ 
    this.<>1__state = 0; 
    this.<>u__1 = taskAwaiter; 
    M.<FooBar>d__0 <FooBar>d__ = this; 
    this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, M.<FooBar>d__0> 
                (ref taskAwaiter, ref <FooBar>d__); 
    return; 
} 

Diese Optimierung überprüft zunächst den Abschluss der Aufgabe. Wenn die Aufgabe nicht abgeschlossen ist, ruft sie UnsafeOnCompleted auf, wodurch die Fortsetzung registriert wird. Wenn es abgeschlossen ist, bricht es die switch und geht an:

this.<>1__state = -2; 
this.<>t__builder.SetResult(); 

die das Ergebnis für die zugrunde liegenden Task setzt, und auf diese Weise Sie tatsächlich den Wert synchron bekommen.

+4

Und es sollte angemerkt werden, dass * das ist eine gute Sache *. Einer der schwierigsten Teile von asynchronem Code ist, dass die meisten asynchronen Operationen synchron abgeschlossen werden können - und das ist ein separater Fall, den Sie behandeln müssen, weil Ihr asynchroner Callback in diesem Fall * nicht * aufgerufen wird. 'await' verbirgt all diese Komplexität vor Ihnen und ermöglicht es Ihnen, die gleiche Aufgabe an zehn verschiedenen Stellen zu" erwarten "(sehr nützlich zum Beispiel für asynchrone faule Initialisierung). – Luaan

+0

kann ich sicher sein, dass die Methode, auf die die Aufgabe verweist, nicht zweimal ausgeführt wird, selbst wenn die Aufgabe bereits ausgeführt wird oder bereits ausgeführt wurde? –

+0

@BilalFazlani Ja, Sie können. Wenn die "Aufgabe" bereits abgeschlossen ist, wird das Ergebnis einfach ausgepackt. –

6

warf ich diesen Schnelltest zusammen:

var i = 0; 
var task = Task.Run(() => { i++; Console.WriteLine(i); return i; }); 

var x = await task; 
var y = await task; 

Console.WriteLine(x); 
Console.WriteLine(y); 

Er schreibt:

1 
1 
1 

Also nur die Aufgabe läuft einmal deutlich.

+0

Dank @Enigmatismus. Das war sehr hilfreich –

Verwandte Themen