文章目錄
-
- 發現問題
- 在事件上直接使用 async 引發的錯誤
- UseTaskFriendlySynchronizationContext 的作用和錯誤引發的原因
- 目前正確的寫法
歡迎到我的部落格中閱讀獨立版本:http://www.dozer.cc/2012/03/async-and-await-in-asp-net-beta/
發現問題
在我的上一篇文章《async 與 await 在 Web 下的應用》中,我提到了 asp.net 4.5 在 Web.Config 中的一個奇怪配置:
<appSettings> <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" /></appSettings>
在 Stack Overflow 上提問後,終於有人回答我了。
看了別人的回複後,才發現了我上篇文章中的問題。
下面代碼中的這種用法是錯誤的:
protected async void Page_Load(object sender, EventArgs e){ WebClient client = new WebClient(); var result1 = await client.DownloadStringTaskAsync("http://www.website.com"); WebClient client2 = new WebClient(); var result2 = await client.DownloadStringTaskAsync(result1); //do more}
在事件上直接使用 async 引發的錯誤碼段一:
public partial class WebForm1 : System.Web.UI.Page{ protected string Msg { get; set; } protected async void Page_Load(object sender, EventArgs e) { using (WebService service = new WebService()) { Msg = await service.Method1TaskSync(); } } protected async void Button_Test_Click(object sender, EventArgs e) { using (WebService service = new WebService()) { Msg = await service.Method2TaskSync(); } }}
試問,最後的 Msg 的值是什嗎?應該是哪個方法的傳回值?
如果去掉非同步,那答案肯定是 Method2。那加上非同步後呢?
這裡用的是 async 和 await 來實現了非同步,所以邏輯上的先後次序應該和代碼上的先後次序一樣。
但是上述代碼兩個事件會一起執行!導致了一定的問題!
總結一下上面代碼的問題:當頁面中的 Page_Load 事件和別的事件都用了 async 和 await 後會出現執行次序錯誤、死結等問題。它們並不會按次序執行。
程式碼片段二:
<appSettings> <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" /></appSettings>
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="AsyncAwait.WebForm1" Async="true" %><!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml"><head runat="server"> <title></title></head><body> <form id="form1" runat="server"> <div> <%:Msg %> </div> </form></body></html>
後端代碼和上面一樣的代碼,只不過把 UseTaskFriendlySynchronizationContext 的配置改成了 true,並且把資料顯示到了頁面上。
執行後發現:根本無法顯示內容,頁面在非同步執行結束前就已經輸出完畢了。
UseTaskFriendlySynchronizationContext 的作用和錯誤引發的原因
其實在老外的回答中已經說明了全部,我這裡主要是翻譯+精簡一下。
UseTaskFriendlySynchronizationContext 的作用:
之前版本的 asp.net 所使用的非同步不符合 CLR 的規範,而只有 RegisterAsyncTask 這個方法是符合 CLR 規範的。
所以 asp.net 4.5 中,加入這個新的配置是為了禁用掉之前不符合約定的功能,只要把這個配置設定為了 true,別的非同步方案全部會失效。(程式碼片段二主要就是示範了這個現象)
引發錯誤的原因:
async 和 await 關鍵字在底層主要是利用 SynchronizationContext 來實現了非同步。(具體原理我也沒研究過)
而這個方案首先不符合 CLR 規範,另外也會引起很多問題。(程式碼片段一主要就是示範了其中一個問題)
目前正確的寫法
首先,建議把 UseTaskFriendlySynchronizationContext 設定為 true。
另外,正確的寫法如下:
public partial class WebForm1 : System.Web.UI.Page{ protected string Msg { get; set; } protected void Page_Load(object sender, EventArgs e) { RegisterAsyncTask(new PageAsyncTask(Method1)); } private async Task Method1() { using (WebService service = new WebService()) { Msg = await service.HelloWorldTaskSync(); } } protected void Button_Test_Click(object sender, EventArgs e) { RegisterAsyncTask(new PageAsyncTask(Method2)); } private async Task Method2() { using (WebService service = new WebService()) { Msg = await service.HelloWorldTaskSync(); } }}
如果需要寫非同步,一定要用 RegisterAsyncTask 方法,實測證明,支援多次調用,而且會按次序執行。
老外說了,他們也想直接在事件上加 async 來寫,但是由於技術原因並沒有實現,希望在正式版或者未來的版本中可以實現吧!
參考資料:
http://stackoverflow.com/questions/9562836/whats-the-meaning-of-usetaskfriendlysynchronizationcontext
http://social.msdn.microsoft.com/Forums/en-NZ/async/thread/b2e8c51e-2808-46d0-92e9-b825321d0af8