看完上篇之後,我們知道await關鍵字後面跟的是一個Task對象,雖然為了看起來很像一個實際方法,Async CTP類庫為現有對象添加的擴充方法沒有使用典型的Get/Make等字眼,更準確的說法應該是await後面應該跟的是一個Await對象,或者是一個有一個public類型的GetAwaiter()方法並且能返回Awaiter對象的對象,這裡所謂的Awaiter對象和我們以往的說法稍微有些區別,因為是編譯器層的文法糖,所以Awaiter對象並沒有實際的意義,比如實現了一個IAwaitable之類的介面,它僅僅要求有一個BeginAwait和EndAwait方法。
看下面的程式碼片段:
1: await 1000;
我們希望能非同步等待1000毫秒,編譯器當然毫不客氣的拒絕了我們的要求,因為從1000這個數字無法獲得一個Awaiter(為了簡潔,後面沿用這個說法,準確的說法應該是沒有從中找到BeginAwait和EndAwait方法),為了達到我們的目的,我們利用Task給int添加一個GetAwaiter方法:
1: public static class IntExtensions
2: {
3: public static TaskAwaiter<int> GetAwaiter(this int s)
4: {
5: TaskCompletionSource<int> tsc = new TaskCompletionSource<int>();
6: Task<int> task = tsc.Task;
7:
8: tsc.TrySetResult(0);
9:
10: return task.GetAwaiter<0>();
11: }
12: }
這樣,await 1000被編譯器認同,我們的代碼能順利執行,但是我們還沒有為Task指定執行我們需要的等待1000毫秒,所以await會馬上返回。這裡先不涉及Task的內容,我們使用另外一個方法,直接為Int類型實現BeginAwait和EndAwait兩個方法,或者單獨定義一個IntAwaiter類,上面的代碼簡單的修改為:
1: public static IntAwaiter GetAwaiter(this int i)
2: {
3: return new IntAwaiter(i);
4: }
而IntAwaiter類的定義如下:
1: public class IntAwaiter
2: {
3: SynchronizationContext _UIContext = SynchronizationContext.Current;
4:
5: int _Value = 0;
6:
7: public IntAwaiter(int i)
8: {
9: _Value = i;
10: }
11:
12: public bool BeginAwait(Action continuation)
13: {
14: if (SynchronizationContext.Current != _UIContext)
15: {
16: return false;
17: }
18: else
19: {
20: ThreadPool.QueueUserWorkItem((state) =>
21: {
22: Thread.Sleep(_Value);
23: _Value = 5;
24: _UIContext.Send(new SendOrPostCallback((target) => { continuation(); }), null);
25: });
26: return true;
27: }
28: }
29:
30: public int EndAwait()
31: {
32: return _Value;
33: }
34: }
然後我們來寫幾句代碼測試一下:
1: listBox1.Items.Add(DateTime.Now);
2: await 1000;
3: listBox1.Items.Add(DateTime.Now);
4: await 5000;
5: listBox1.Items.Add(DateTime.Now);
結果如下:
IntAwaiter非常簡單,實際上Async CTP中的TaskAwaiter也基本上就做了這些事,在BeginAwait中啟動Task,在EndAwait中判斷Task狀態,如果成功完成,返回Task的Result對象,如果是Cancel或者Fault,則拋出異常。
有了前文和本文的瞭解,實際上如果一個應用中的非同步方法呼叫是自己實現的而非像WCF一樣由VS自動產生的代理類,那麼完全可以在代碼中處理async達到和await一樣簡潔的代碼風格而不是讓編譯器來幹這事,畢竟如果不是真的無法取代我不願意帶上130K的這傢伙,而且Preview版無法調試await也很讓人頭疼。