介紹的部份請至 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"; }); // 也可以調用非同步方法
}
}
感謝分享!!
回覆刪除