排程 Task & 非同步 Async / Await
關鍵字
- Task : 是讓指定的Delegate 以另一個線程來執行.
- async : 指定目標delegate 為存在非同步語法的方法,回傳值改變為Task & Task<T> (這裡不建議以void 作為回傳值(註1))
- await : 等待目標排程結束
理解方法
- 遇到Task 時會另開線程與當下線程一起往下執行
- 遇到await 時程式會等待目標Task完成
- await Task.Run 為等待一個新開的排程
- 等待時不見得指有這個排程在執行 可能還有其他尚未進入等待的排程也在執行
- async 是 可以讓method 中的 await Task.Run 簡化成為 await 的method 關鍵字
- 從單一個排程的視角來看程式的運行流程仍然沒有改變 只是多了一個等待的通知繼續進行的狀態(如果看不懂那張複雜的圖的話)
- 需要圖輔助的話,可以以下面這張圖來做為參考
-------------------> 主線程時間軸
單線程
------------------->
非同步(非多線程同時進行)
----->| |-----> 中間為等待的時間 (用 await)
|----->| 另一個排程開始運作並結束 (用Task)
多線程
-------------------> 主線程於中途創造了兩個排程(用Task) 但並未等待任何一個排程
|--------> 排程一執行結束
|--------> 排程二執行結束
非同步與多線程交錯情境
----->| |----->| |-------> M M建立T1,T3 並都於建立時等待 (用 await Task)
|--->| T1 T1建立T2但沒有進行等待(用 Task)
|-------| |----> T2 T2建立了T4但是並未立即進行等待進行等待(先用task 後面才await)
|-->| T3 T3建立後運行結束
|----->| T4 T4建立後與T2一起運行 中途T2進入等待直到T4結束
使用方法
public void NonAsyncMethod()
{
Task task = Task.Run(() => DemoAction()); // 基本使用方法Task.Run中的Action or Func<T>
// 會在另一個線程完成,排程會回傳型別為Task的物件
Task<object> resultTask = Task.Run<object>(() => DemoFunc()); // 具有回傳值的非同步方法則用泛型指定回傳型別
// 具回傳值的排程型別為Task<T>
//resultTask.Wait(); // Wait 會鎖死當下線程 所以使用不當容易造成deadlock 故不建議使用 07/23/2017
// 當需要回傳值的時候必須以Wait method 等待排程完成
// 否則通常都會拿到null (因為回傳值還沒回來)
object result = resultTask.Result; // 以Task.Result取得回傳值
// Task.Result 即為 等待並取回Result 7/23/2017 by 余小章大大
// 於multi await 時 似乎有些狀況 尚未查證
// 無回傳值的Task 可以無需執行Wait方法
// await task; 也同樣可以利用Wait 方法等待排程完成後 在繼續往下執行
}
async public Task InvokeAsync() //async 標示法可以寫在開頭,個人建議如已知包含非同步內容的method加入Async在name後面
{
await DemoTask(); // 於標示為非同步方法的方法中 可以用await 來取代 Task.Wait()
var result = await DemoFuncTask(); // 亦可以使用await 來直接得到 Task<T>.Wait() 後的 Task.Result
Task.Run(() => DemoTask()) // 於非同步標記的方法中亦可以使用Task.Run 來另開排程,
// 那此排程就會與當下的線程走不同線程
await Task.Run(() => DemoFuncTask()); // 當然也可以宣告Task.Run之後用await等待
// 只是這與 await DemoTask()並無差異
result = await Task.Run<obejct>(() => { // 所以通常會要這樣用是因為使用了lambda 來臨時執行多個 method
DemoAction();
return DemoFunc(); // 這裡一樣可以回傳所需的回傳值
});
result = await Task.Run(DemoActionManyMethod); // 如果不想寫lambda 寫成Method 並帶入名稱也是可以的
// 因為仍然沒有等待中途執行的 Task.Run(() => DemoTask()) 所以該排程仍然在持續進行中
// 以此可以做出類似MultiThread的效果
}
// Property Value 為 Delegate時亦可以把Property當Method呼叫
// 當無回傳值的Task,且無標示async的時候,可回傳Task.CompletedTask 以作為完成工作的回傳Task
public Func<Task> DemoActionTask => () => Task.CompletedTask;
// 無標示async 且具有Task<T>回傳值的方法可用Task.FromResult<T>(T t)來把結果回傳
public Func<Task<object>> DemoFuncTask => () => Task.FromResult<object>(new object())
// ConfigureAwait(bool) 的意思是指設定是否此awaiter 需要留存上下文 預設為false(沒寫亦為false)
// 這裡的上下文是指於排程建立時的那個線程的SynchronizationContext之類 會方法執行前後有關連性的東西
// ex: TraceStack (呼叫堆疊),如果ConfigureAwait(true) 時,可以看到最前面的堆疊,反之只能看到排程開始時的堆疊
// 建議ConfigureAwait(false)主要還是效能首選考量
// 但如因為不留上下文而讓開發時難以追蹤問題的話 不訪設定變數來統一開關這個flag 也不失一方法
public void ConfigAwaitDemo() => Task.Run(DemoAction).ConfigureAwait(false);
// 以下demo 用示範用例
public void DemoActionManyMethod() {
DemoAction();
DemoAction();
DemoAction();
}
public Action DemoAction => () => {}
public Func<object> DemoFunc => () => new Object();