介紹的部份請至 MSDN 瀏覽:Asynchronous Programming with Async and Await
這裡簡單的寫些範例說明傳統的 delegate 與使用 async 寫法的差異,及使用 async 時的注意事項。
第一個範例,按下 Button 後使用傳統的 ThreadPool 執行一個長達 3 秒的背景作業,該作業內會每隔 1.5 秒印出一些文字及時間。可以從程式碼發現我們總共要寫兩個接受委派的 Method 分別名為 ActionWork 與 ActionCompleted。並且可以注意到 ActionWork 因為不在 UI Thread 執行,所以內部任何要在 lbl 這個 TextBlock 上輸出資訊的指令都必須使用 Dispatcher.RunAsync 包起來以確保不會發生 ThreadException,這個範例執行完成的輸出資訊看起來很直觀,Button Click Start 與 Button Click End 在 Background Thread 執行之前就先被執行輸出了。
private void OnButtonClick(object sender, RoutedEventArgs e)
{
    lbl.Text = "Button Click Start\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
    IAsyncAction action = ThreadPool.RunAsync(ActionWork, WorkItemPriority.Normal);
    action.Completed = ActionCompleted;
    lbl.Text = lbl.Text + "Button Click End\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
}
private void ActionWork(IAsyncAction sender)
{
    Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        lbl.Text = lbl.Text + "Delay Start\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
    });
    DateTime dt = DateTime.Now;
    while(true)
    {
        if ((DateTime.Now - dt) > TimeSpan.FromSeconds(1.5))
        {
            break;
        }
    }
    Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        lbl.Text = lbl.Text + "Delaying\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
    });
    dt = DateTime.Now;
    while (true)
    {
        if ((DateTime.Now - dt) > TimeSpan.FromSeconds(1.5))
        {
            break;
        }
    }
}
private void ActionCompleted(IAsyncAction source, AsyncStatus status)
{
    Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        lbl.Text = lbl.Text + "Delay End\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
    });
}
// 輸出結果
Button Click Start
2012/11/30 - 23:28:17
      
Button Click End
2012/11/30 - 23:28:17
Delay Start
2012/11/30 - 23:28:17
Delaying
2012/11/30 - 23:28:18
Delay End
2012/11/30 - 23:28:20
第二個範例,我們改用 async 修飾 Delay 這個 Method,並且在按鍵事件中直接調用 Delay,可以看到輸出結果好像沒什麼差異,但至少我們可以知道 Delay 的確是在 Background Thread 執行的,而且在 Delay 裡面我們可以很方便的直接更新 UI 上的資訊。
private void OnButtonClick(object sender, RoutedEventArgs e)
{
    lbl.Text = "Button Click Start\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
    Delay();
    lbl.Text = lbl.Text + "Button Click End\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
}
private async void Delay()
{
    lbl.Text = lbl.Text + "Delay Start\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
    await Task.Delay(1500);
    lbl.Text = lbl.Text + "Delaying\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
    await Task.Delay(1500);
    lbl.Text = lbl.Text + "Delay End\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
}
// 輸出結果
Button Click Start
2012/11/30 - 23:28:25
Delay Start
2012/11/30 - 23:28:25
Button Click End
2012/11/30 - 23:28:25
Delaying
2012/11/30 - 23:28:26
Delay End
2012/11/30 - 23:28:28
第三個範例我們稍微改了一下 Delay Method 宣告的傳回值,由 void 改為 Task 代表 Delay 由沒有傳回值變成傳回一個任務,執行結果和第二個範例沒什麼差異,但修改回傳值是一件非常重要的事,如果這個非同步的方法是可以被等待的,回傳值就必須為 Task 或 Task<T>。
private void OnButtonClick(object sender, RoutedEventArgs e)
{
    lbl.Text = "Button Click Start\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
    Delay();
    lbl.Text = lbl.Text + "Button Click End\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
}
private async Task Delay()
{
    lbl.Text = lbl.Text + "Delay Start\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
    await Task.Delay(1500);
    lbl.Text = lbl.Text + "Delaying\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
    await Task.Delay(1500);
    lbl.Text = lbl.Text + "Delay End\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
}
// 輸出結果
Button Click Start
2012/11/30 - 23:28:30
Delay Start
2012/11/30 - 23:28:30
Button Click End
2012/11/30 - 23:28:30
Delaying
2012/11/30 - 23:28:31
Delay End
2012/11/30 - 23:28:33
第四個範例基本上和第三個範例很像,只是在按鍵事件裡調用 Delay 時,我們在前面加上了 await 這個關鍵字,可以從執行結果看到明顯的差異,await 發揮了等待非同步執行結果的效用,注意 OnButtonClick 因為內部使用了 await 這個 Keyword 所以也必須被加上 async 作修飾。
private async void OnButtonClick(object sender, RoutedEventArgs e)
{
    lbl.Text = "Button Click Start\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
    await Delay();
    lbl.Text = lbl.Text + "Button Click End\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
}
private async Task Delay()
{
    lbl.Text = lbl.Text + "Delay Start\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
    await Task.Delay(1500);
    lbl.Text = lbl.Text + "Delaying\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
    await Task.Delay(1500);
    lbl.Text = lbl.Text + "Delay End\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
}
// 輸出結果
Button Click Start
2012/11/30 - 23:28:34
Delay Start
2012/11/30 - 23:28:34
Delaying
2012/11/30 - 23:28:36
Delay End
2012/11/30 - 23:28:37
Button Click End
2012/11/30 - 23:28:37
第五個範例,我們將 Delay 回傳值修改為 Task<String> 代表這個非同任務會回傳字串,在按鍵事件中我們也使用 await 來接非同步方法的回傳值,可以看到輸出的順序和第四個範例一樣,但是輸出步驟有差異,除了 Button Click Start 之外的四行幾乎是同時印出來的,因為我們把 Delay 內的資訊組為字串回傳給按鍵事件做輸出。
private async void OnButtonClick(object sender, RoutedEventArgs e)
{
    lbl.Text = "Button Click Start\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
    String strResult = await Delay();
    lbl.Text = lbl.Text + strResult + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
    lbl.Text = lbl.Text + "Button Click End\n" + DateTime.Now.ToString("yyyy/MM/dd - hh:mm:ss") + "\n\n";
}
private async Task<String> Delay()
{
    String strRes = "";
    strRes = strRes + "Delay Start\n";
    await Task.Delay(1500);
    strRes = strRes + "Delaying\n";
    await Task.Delay(1500);
    strRes = strRes + "Delay End\n";
    return strRes;
}
// 輸出結果
Button Click Start
2012/11/30 - 23:28:41
Delay Start
Delaying
Delay End
2012/11/30 - 23:28:44
Button Click End
2012/11/30 - 23:28:44
可以發現將傳統非同步 + 委派處理的流程修改為 async / await 方法是非常容易的,async 可以很隨性的使用,任何匿名、委派、甚至 override 的方法都可以加上去,例如以下範例顯示我們可以加上 async 延伸出包含非同步方法的子類別,非常的方便好用,真的會上癮。
public abstract class BaseTask
{
    public abstract void Run1();
    public abstract void Run2();
    public abstract Task<String> Run3();
}
public class SubTask : BaseTask
{
    public override void Run1()
    {
    }
    public override async void Run2()
    {
        await Task.Delay(3000);
    }
    public override async Task<String> Run3()
    {
        // return "Ascii"; // 可以直接回傳字串
        return await Task<String>.Factory.StartNew(() => { return "Ascii"; }); // 也可以調用非同步方法
    }
}
 
感謝分享!!
回覆刪除