摘要:[Concurrency]ConfigureAwait(False)另開context的功用
ConfigureAwati(False)的主要功用是,讓async非同步的 "library" function的await不用caller的context(記憶體)而是用自己的context去儲存執行的狀態,是為了增加concurrency效能而存在,並且能防止不當的同步呼叫library function導致的deadlock,注意:Stephen強調,ConfigureAwait(False)一定只能加在library function,不可加在User interface thread(例如ooxx_click()裡面就絕對不能加)。
StephenCleary指出, 通常是caller端的async + await以及library function端的ConfigureAwait(False)可以達成最大的concurrency效能,單獨做其中一項的話,concurrency效能都沒那麼好。
首先是會產生deadlock的例子,流程是這樣的:
caller在click之後直接進入DoSomethingAsync之後,將記錄執行狀態的context交給DoSomethingAsync裡面的await Task.Delay(TimeSpan.FromSeconds(3));使用, 而await Task.Delay(TimeSpan.FromSeconds(3));因為是非同步執行,所以不會立刻執行完畢(3秒之後才執行完),因此記錄下目前的執行狀態到caller的context(因為目前caller與library function共用context)之後,將執行控制權還給caller(在這個例子中,第二個await Task.Delay(TimeSpan.FromSeconds(3));是執行不到的),一直到caller端執行完畢string cde = "";,這時候Page.ClientScript會用到DoSomethingAsync的結果,因此不得不到Context檢查一下是否DoSomethingAsync執行完畢了沒,因此caller是以同步的方式在執行中,因此就死捏著context資源不放,過了3秒之後,第一個await Task.Delay(TimeSpan.FromSeconds(3));總算執行完了,library function想要繼續後面的val *= 2;,因此必須先到context讀取回之前的執行狀態才能繼續執行下去,但是caller死捏著不放,因此就變成caller等library function執行完,library function也等caller執行完,就變成deadlock了。
.aspx:
<asp:Button ID="btnSyncWithoutConfigureAwait" runat="server" Text="SyncWithoutConfigureAwait" OnClick="btnSyncWithoutConfigureAwait_Click" />
code_behind:
//此例會造成Deadlock
//ps.以同步的方式執行非同步的function是非常不建議的
protected void btnSyncWithoutConfigureAwait_Click(object sender, EventArgs e)
{
Task tVal = DoSomethingAsync();
string abc = "";
string cde = "";
Page.ClientScript.RegisterStartupScript(this.GetType(), "", "alert('" + tVal.Result.ToString() + "');", true);
}
private async Task DoSomethingAsync()
{
int val = 13;
// Asynchronously wait 3 second.
//只是為了讓此library function為非同步執行才用Task.Delay,實際尚無特別意義
await Task.Delay(TimeSpan.FromSeconds(3));
val *= 2;
// Asynchronously wait 3 second.
await Task.Delay(TimeSpan.FromSeconds(3));
return val;
}
修改上例子之後,在library function加入ConfigureAwait(false)就不會造成deadlock了,因為library function另開記憶體儲存context了,如下:
.aspx:
<asp:Button ID="btnSyncWithConfigureAwait" runat="server" Text="SyncWithConfigureAwait" OnClick="btnSyncWithConfigureAwait_Click" />
code_behind:
protected void btnSyncWithConfigureAwait_Click(object sender, EventArgs e)
{
Task tVal = DoSomethingAsyncWithConfigureAwait();
string abc = "";
string cde = "";
Page.ClientScript.RegisterStartupScript(this.GetType(), "", "alert('" + tVal.Result.ToString() + "');", true);
}
//.ConfigureAwait(false)另開了context以儲存library function的執行狀態
private async Task DoSomethingAsyncWithConfigureAwait()
{
int val = 13;
// Asynchronously wait 3 second.
//增加了.ConfigureAwait(false)另開了context
await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
val *= 2;
// Asynchronously wait 3 second.
//增加了.ConfigureAwait(false)另開了context
await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
return val;
}
最後仍要補充一下,Stephen強調,caller以非同步的方式呼叫加上ConfigureAwait(false)的非同步的library function才是最推薦的,雖然不論是library function單獨加上ConfigureAwait(False)或是單獨caller端以非同步的await執行library function都可以解決deadlock,但是兩者並行才可取得最大的concurrency效能, 如下:
ps.Stephen也強調,ConfigureAwait(false)一定只能加在library function, 千萬不要加在caller thread(例如UI端的程式碼ooxx_click()裡面絕對不能加)
.aspx
<asp:Button ID="btnAsyncWithConfigureAwait" runat="server" Text="AsyncWithConfigureAwait" OnClick="btnAsyncWithConfigureAwait_Click" />
code_behind:
protected async void btnAsyncWithConfigureAwait_Click(object sender, EventArgs e)
{
int intVal = await DoSomethingAsyncWithConfigureAwait();
string abc = "";
string cde = "";
Page.ClientScript.RegisterStartupScript(this.GetType(), "", "alert('" + intVal.ToString() + "');", true);
}
//DoSomethingAsyncWithConfigureAwait跟上面一樣,所以就不列出了
ConfigureAwait(false)介紹,大概是這樣
完整程式碼看這:
http://saltsourcecenter.blogspot.tw/2015/10/configureawaitaspx.html
參考書籍:
Concurrency in C# Cookbook
http://shop.oreilly.com/product/0636920030171.do
參考文章:
Don't Block on Async Code
http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html