1. 程式人生 > >.NET 基於Task的非同步程式設計模型

.NET 基於Task的非同步程式設計模型

最近下載了Visual Studio Async CTP,體驗了下基於Task的非同步程式設計帶來的新特性。在CTP中,增加了新的關鍵字: async, await。尤其是在SL,WP7的程式設計中,大量使用非同步呼叫的環境裡,async, await的確能減少程式設計的複雜度。看上去像是同步的方法,其實編譯器做了些手腳,悄悄的生成了回撥的程式碼。比如:

private async void button_Click(object sender, EventArgs e)
{
    var client = new WebClient();
    var result = await client.DownloadStringTaskAsync("http://www.csdn.net");
    textBox1.Text = result;
    MessageBox.Show("Complete");
}
上面的程式碼,await 這一行的 DownloadStringTaskAsync (CTP中AsyncCtpLibrary.dll中提供的WebClient的擴充套件方法) 是非同步執行的,之後兩行被包裝成回撥。
(另外,注意:在button_Click的void之前加上了async關鍵字)


按照原來的寫法:
private void button3_Click(object sender, EventArgs e)
{
    var client = new WebClient();
    client.DownloadStringCompleted += (s, evt) => {
        textBox1.Text = evt.Result;
        MessageBox.Show("Complete");
    };
    client.DownloadStringAsync(new Uri("http://www.csdn.net"));
}
比較程式碼可以感覺到 async,await 的出現,一下把我們從“先定義回撥”的思想變回“同步呼叫"的順序時代。
先回顧一下目前為止我們使用的非同步程式設計方法:

1. 最簡單的Thread:

var thread = new Thread((obj) =>
{
    // 模擬複雜的處理
    Thread.Sleep(1000);
    Console.WriteLine(obj);
});
thread.Start("some work");
這種處理方式,麻煩在於處理返回值上,通常還要設計個包裝類封裝個Result 屬性以獲得返回值。

2. Thread的包裝演變出 APM  (Asynchronous Programming Model):

最典型的代表:Delegate.BeginInvoke / EndInvoke。WCF的客戶端代理類如果選擇生成非同步方法,那麼也是BeginXXX,EndXXX這樣的方法。
Func<string, string> func = x =>
{
    // 模擬複雜的處理
    Thread.Sleep(1000);
    return x + " is completed";
};
func.BeginInvoke("some work", ir =>
{
    AsyncResult ar = (AsyncResult)ir;
    var delegateInstance = (Func<string, string>)ar.AsyncDelegate;
    var result = delegateInstance.EndInvoke(ir);
    Console.WriteLine(result);
}, null);

3. 基於事件的APM——EAP(Event-based Asynchronous Pattern)
上面的BeginXXX,EndXXX的非同步程式設計模型,在Callback上還是略顯笨重,因此又演變出基於事件註冊回撥方法的模式。
最典型的代表就是 WebClient (HttpWebRequest 等)
var client = new WebClient();
client.DownloadStringCompleted += (s, evt) => {
    textBox1.Text = evt.Result;
    MessageBox.Show("Complete");
};
client.DownloadStringAsync(new Uri("http://www.csdn.net"));

4. 基於Task的APM——TAP(Task-based Asynchronous Pattern)
.net 4.0 裡引入了並行程式設計庫,Task成為新的非同步程式設計主角,async, await 語法糖應運而生。為了實現async,await編譯器將每個被async關鍵字標記的方法編譯為一個方法所在類的一個內嵌類,所有在方法體內出現的變數會被轉為這個類的欄位,如果是一個例項方法,那麼this所代表的物件也被宣告為一個欄位。這個類有兩個核心成員:一個int來儲存程式碼執行到哪一步的state,一個方法來執行真正的動作的 MoveNext() 方法。

比如下面的方法:

static async void SimpleAsyncTest()
{ 
    var client = new WebClient();
    var result1 = await client.DownloadStringTaskAsync("http://www.csdn.net");
    Console.WriteLine("complete");
}
被轉化為如下:注:Reflector反射出來的名字都是特殊字元,下面的類名變數名為了可讀已做替換。
private static void SimpleAsyncTest()
{
    SimpleAsyncTestObj obj = new SimpleAsyncTestObj(0);
    obj.MoveNextDelegate = new Action(obj, (IntPtr)this.MoveNext);
    obj.builder = AsyncVoidMethodBuilder.Create();
    obj.MoveNext();
}

SimpleAsyncTestObj:

[CompilerGenerated]
private sealed class SimpleAsyncTestObj
{
    // Fields
    private bool disposing;
    public AsyncVoidMethodBuilder builder;
    private int state;
    public Action MoveNextDelegate;
    private TaskAwaiter<string> awaiter;
    public WebClient client;
    public string result;

    // Methods
    [DebuggerHidden]
    public SimpleAsyncTestObj(int state);
    [DebuggerHidden]
    public void Dispose();
    public void MoveNext();
}

可以看到async方法被轉化成類,然後呼叫了 MoveNext() ,初始的 state == 0,第一次呼叫如果沒有 IsCompleted 則註冊 awaiter 的 OnCompleted 事件,繫結的還是 MoveNext 方法。等awaiter 回撥時,狀態==1,直接進入 Label_0086 部分的程式碼,用 GetResult() 獲取結果。

public void MoveNext()
{
        try
        {
            string str;
            bool flag = true;
            if (this.state != 1)
            {
                if (this.state != -1)
                {
                    this.client = new WebClient();
                    this.awaiter = this.client.DownloadStringTaskAsync("http://www.csdn.net").GetAwaiter<string>();
                    if (this.awaiter.IsCompleted)
                    {
                        goto Label_0086;
                    }
                    this.state = 1;
                    flag = false;
                    this.awaiter.OnCompleted(this.MoveNextDelegate);
                }
                return;
            }
            this.state = 0;
        Label_0086:
            str = this.awaiter.GetResult();
            this.awaiter = new TaskAwaiter<string>();
            this.result = str;
            Console.WriteLine("over");
        }
        catch (Exception exception)
        {
            this.state = -1;
            this.builder.SetException(exception);
            return;
        }
        this.state = -1;
        this.builder.SetResult();
}

如果方法中有多個 await 關鍵字的話,編譯器生成的 MoveNext 則會是下面的樣子:
public void MoveNext()
{
    switch (state)
    {
        case 1:
            ...
            state++;
            // 註冊第一個await對應非同步的回撥處理:回撥中,獲取結果,並呼叫下一個非同步
            break;
        case 2:
            ...
            state++;
            // 註冊第二個await對應非同步的回撥處理:回撥中,獲取結果,並呼叫下一個非同步
            break;
        case 3:
            ....
            state++;
            break;
         。。。
    }
}
通過回撥把下一個非同步串起來,這麼看來確實有 yield return 的感覺。看到這裡相信大家都看出來在使用 TAP 程式設計時,最重要的是進行Task的設計,比如:

static async void DoSomeWork(int i, string arg)
{
    await new TaskFactory().StartNew(() =>
    {
        var random = new Random();
        // 模擬複雜的處理
        Thread.Sleep(i * 1000);
        Console.WriteLine(arg + " end");
    });
}
還可以利用 TaskFactory.ContinueWhenAll / ContinueWhenAny 等特性編排 Task。 以實現更加靈活的非同步程式設計。
更多的 CTP Sample 可以看這裡: http://www.wischik.com/lu/AsyncSilverlight/AsyncSamples.html