假設閱讀本文的您已經對await/async有了一定了解。
.net中的新關鍵字await出來很久,剛接觸時心里對它非常抵觸,習慣了用過去的異步模型,感覺已經足夠我實現各種想要的功能,認為新出的await簡化了代碼,必然犧牲代碼的靈活性。這段時間公司項目不忙,周圍同事一起討論await用法,討論之間發現大家對其理解各不相同,這……激發我對await的深入研究的興趣。
首先說幾點常見的誤區,隨后介紹下await的高級用法。
誤區一:帶有async/await關鍵字的方法難道就是異步方法了嗎?
答案是:不是,有async關鍵字只是告訴編譯器我這個方法可以用await。實際上沒發起任何異步操作,
即使再有await也不是用來發起異步操作的。await僅僅是一個高級的handler。
誤區二: 我們通常只用await后跟一個方法例如:
await SomeMethod();
await不是用來調用方法的前綴。它是用來接收一個異步的返回值例如Task;
可以這樣寫:
var result=SomeMethod();
await result;
誤區三:await的控制流:微軟做了大量的努力通過await/async關鍵字,把之前的前后顛倒,跳來跳去的控制流串成我們代碼所寫的順序,
為了增強閱讀性,讓我們易於調試和開發。很多人(包括之前的我)真的以為帶有async的函數就是一個線程的順序執行的控制流。
實際上不是那樣。副一張圖介紹下。
實線箭頭為主線程的控制流,虛線箭頭為接收異步回調開啟的新線程的控制流。再看一個例子。
現在我們避免了上面的誤區可以更自由的發揮了。
最后一個介紹才是本文的重頭戲:
await的高級應用,自定義awaiter。讓我們的await可以await一切。
之前我們的await只能這樣用的:
await SomeMethod();
await Task.delay(5000);
或者
var result=SomeMethod();
await result;
大家有沒有這樣用過?
await 5000;//延遲5秒;等於awaitTask.delay(5000);
string s=await "http://www.hanselman.com/blog/NuGetPackageOfTheWeek13PortableHttpClientMakesPortableLibrariesMoreUseful.aspx";//get這個地址的html;
甚至可以這樣
Task.Run(()=>{
string s="hello";
this.SomeTextBlock.Text=s;
});
上面的寫法肯定是會報錯的,因為不能從其他線程給UI線程的控件賦值。
我自己封裝了一個線程同步的方法:
Task.Run(()=>{
string s="hello";
await this.SomeTextBlock;
this.SomeTextBlock.Text=s;
});
await一下這個想要被操作的控件就可以賦值了。
是不是很過癮?
我來一步一步告訴大家怎么樣實現。
await接收的是TaskAwaiter 或TaskAwaiter <T> 的返回值 所以我們定義一個擴展方法可以讓await當做自定義類型的前綴。
public static class AwaitHelper { //public static TaskAwaiter GetAwaiter(this TimeSpan timeSpan) //{ // return TaskEx.Delay(timeSpan).GetAwaiter(); //} public static TaskAwaiter GetAwaiter(this int ms) { return Task.Delay(ms).GetAwaiter(); } public static TaskAwaiter GetAwaiter(this string s) { return new Task(() => { }).GetAwaiter(); } public static AllanControlAwaiter GetAwaiter(this DependencyObject control) { return new AllanControlAwaiter(control); } }
看到這里我們就知道
await 5000;//延遲5秒;
是怎么實現的吧。
下面我們繼續封裝一個AllanControlAwaiter實現線程同步操作
public class AllanControlAwaiter : INotifyCompletion { private readonly DependencyObject m_control; private bool _IsCompleted = false; public AllanControlAwaiter(DependencyObject control) { m_control = control; } public bool IsCompleted { get { return _IsCompleted; } } public void OnCompleted(Action continuation) { var result = m_control.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => continuation()); result.AsTask().Wait(); _IsCompleted = true; } public void GetResult() { } }
到這里大功告成。
快去體驗一下可以await一切的感覺吧!