有一個任務變量,可以說任務正在運行..通過執行以下行。如果我等待已經運行或正在運行的任務,會發生什麼情況?
await _task;
我在想,當我寫這篇文章的代碼會發生什麼:
await _task;
await _task;
將它執行任務的兩倍?或拋出一個異常,因爲它已經運行?
有一個任務變量,可以說任務正在運行..通過執行以下行。如果我等待已經運行或正在運行的任務,會發生什麼情況?
await _task;
我在想,當我寫這篇文章的代碼會發生什麼:
await _task;
await _task;
將它執行任務的兩倍?或拋出一個異常,因爲它已經運行?
它會執行兩次任務嗎?或拋出異常,因爲它已經運行了 ?
否和否。 await
唯一能做的就是撥打Task.GetAwaiter
,它不會導致任何事情發生。如果任務已經運行完成,它將提取該值(如果它是Task<T>
),或者如果它是Task
,則同步運行到下一行,因爲已經完成了已完成的任務的優化。
一個簡單的演示:
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;
}
如果你深入到國家機器本身,你會看到這一點:
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;
}
這種優化首先檢查任務的完成。如果任務未完成,它將調用UnsafeOnCompleted
這將註冊延續。如果它是完整的,它打破了switch
並進入:
this.<>1__state = -2;
this.<>t__builder.SetResult();
,用於設置基本Task
的結果,這樣你實際上得到的值同步。
應該指出的是*這是一件好事*。異步代碼中棘手的部分之一是大多數異步操作事實上可以同步完成 - 這是一個單獨的情況,您必須處理,因爲在這種情況下,異步回調不會被調用。 'await'隱藏了你所有這些複雜性,並且它允許你在十個不同的地方「等待」相同的任務(對於例如異步延遲初始化非常有用)。 – Luaan
我可以確定,即使任務正在運行或已經運行,任務指定的方法也不會執行兩次嗎? –
@BilalFazlani是的,你可以。如果「任務」已完成,它將簡單地打開結果。 –
我扔在一起這個簡單的測試:
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);
它寫道:
1
1
1
因此,很明顯,這個任務只運行一次。
謝謝@Enigmativity。這真的很有幫助 –
你爲什麼不嘗試呢?這不難測試。 – Enigmativity
這裏的關鍵洞察是* await不會運行任務*。等待*異步等待任務完成*。因此名稱「等待」。有一個持續的神話,等待開始一項任務,但它當然不是;當你等待它時,你已經有了一個開始的任務。 –
真正讓我大開眼界的是「延續」的概念。我認爲沒有這種洞察力,很難理解「等待」。 – Sentinel