咱們要把上面這個Task封裝成方法,怎麼辦?api
最重要的一點,這個方法要能返回生成的random,後面的代碼要用!數組
public static Task<int> getRandom() { return Task<int>.Run(() => { Thread.Sleep(500); //模擬耗時 return new Random().Next(); }); }安全
@想想@:應該如何調用這個方法?(_提示:不要直接__getRandom().Result_)架構
假如咱們還須要進一步的封裝,添加一個方法Process,裏面調用getRandom()並把其結果輸出:併發
public static void Process() { Task<int> task = getRandom(); Console.WriteLine(task.Result); }dom
故技重施,好像不行了,此次……異步
@想想@:再讓Process()返回Task行不行?一個Task套另外一Task會出現什麼狀況?async
在getRandom()和Process()中展現線程Id看一看:ide
Console.WriteLine("in getRandom() with Thread-" + Thread.CurrentThread.ManagedThreadId);性能
在.NET core的I/O類庫中,咱們會發現這樣的方法:
public static Task AppendAllLinesAsync(string path, IEnumerable<string> contents, Encoding encoding, CancellationToken cancellationToken = default); public static Task<byte[]> ReadAllBytesAsync(string path, CancellationToken cancellationToken = default);
注意:
.NET爲咱們提供了簡潔優雅的異步方法,只須要兩個關鍵字:
被async標記的方法被稱爲異步方法,
只有await沒有async,報編譯錯誤。
static async void Process() { int random = await getRandom(); Console.WriteLine(random); }
await,能夠理解爲:異步(async)等待,後接 awaitable 實例。
咱們能夠簡單的把awaitable理解成Task。
異步方法一直同步運行,直到 await。
從 await 開始異步(分叉):
直到 awaitable 中內容執行完畢,才暫停方法調用處內容,繼續執行await以後的代碼。
異步方法執行完畢,繼續方法調用處內容。
以上述代碼爲例:
//33 --> 44 --> 45 --> 46 --> 47 --> 調用異步方法處 // +--> 52 --> 57-- + 被調用異步方法 // +--> 35 --> 38 +--> 39 awaitable
注意:若是52行以前還有普通(非異步)代碼,這些代碼不會被異步執行。
await不像Wait()或Result同樣,
開始(但不是當即或同步的)
async和await會不會開啓一個新的任務(或者線程)?不會。
異步方法分爲兩種:
返回 void 或 Task
public static async void Getup() { Console.WriteLine($"before await-1 with thread {Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"before await-2 with thread {Thread.CurrentThread.ManagedThreadId}"); //await 以前的代碼,在主線程上運行 // await Task.Run(()=> { Console.WriteLine($"in await with thread {Thread.CurrentThread.ManagedThreadId}"); }); // Console.WriteLine($"after await-3 with thread {Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"after await-4 with thread {Thread.CurrentThread.ManagedThreadId}"); }
從await開始,代碼開始分叉(只是異步,不必定新開線程):
直到await後的Task執行完畢,纔會返回async方法,繼續執行其await(非阻塞)以後的剩餘代碼。
Console.WriteLine($"before async-1 with thread {Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"before async-2 with thread {Thread.CurrentThread.ManagedThreadId}"); Getup(); for (int i = 0; i < 10; i++) { //Getup()裏await部分的運行,會打亂這裏代碼的同步運行 Console.WriteLine($"after async-{3 + i} with thread {Thread.CurrentThread.ManagedThreadId}"); }
實質上,await採用的是Task的ContinueWith()機制:await 以後的方法內代碼,被 await Task 執行完畢後調用。
對比演示:
異步方法中的 void 能夠被直接替換成 Task(推薦),以便於該方法進一步的被 await 傳遞。
void一般作爲頂級(top-level)方法使用。
思考:當async方法中拋出異常,void方法和Task方法的區別?
返回Task<T>
返回值被Task包裹,寫成Task<T>,T指方法體內聲明返回的類型
//方法的聲明:返回的是Task<int> public static async Task<int> Getup() { int result = await Task<int>.Run(() => { Thread.Sleep(500); Console.WriteLine($"at await in Getup() with thread {Thread.CurrentThread.ManagedThreadId}"); return new Random().Next(); }); //方法體內,返回的是int return result; }
特別注意:不能直接Getup().Result 或 await Getup()取值,不然……
思考:和直接返回Task<T>的區別?
任務並行庫(Task Parallel Library)
.NET中System.Threading 和System.Threading.Tasks名稱空間下的類庫
簡化異步/並行開發,在底層實現:
基於Task的並行
最簡單的例子,Parallel.Invoke():
for (int i = 0; i < 5; i++) { Console.WriteLine(); Parallel.Invoke( () => { Console.WriteLine(i + $":task-{Task.CurrentId} in thread-{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"task-{Task.CurrentId} begin in thread-{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"task-{Task.CurrentId} in thread-{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"task-{Task.CurrentId} end in thread-{Thread.CurrentThread.ManagedThreadId}"); }, () => { Console.WriteLine(i + $":task-{Task.CurrentId} in thread-{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"task-{Task.CurrentId} in begin in thread-{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"task-{Task.CurrentId} in thread-{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"task-{Task.CurrentId} in end in thread-{Thread.CurrentThread.ManagedThreadId}"); } ); }
其餘方法:
Parallel.For(0, 10, x => { Console.WriteLine(x); });
Parallel.ForEach(Enumerable.Range(1,10), x => Console.WriteLine(x));
引入線程數組:Task[]
對比如下代碼,體會 await 的 continuation:
public static async Task Getup() { //await Task.Run(() => { Console.WriteLine("洗臉"); }); //await Task.Run(() => { Console.WriteLine("刷牙"); }); //await Task.Run(() => { Console.WriteLine("吃早餐"); }); //await Task.Run(() => { Console.WriteLine("背單詞"); }); Task[] tasks = { Task.Run(() => { Console.WriteLine("洗臉"); }), Task.Run(() => { Console.WriteLine("刷牙"); }), Task.Run(() => { Console.WriteLine("吃早餐"); }), Task.Run(() => { Console.WriteLine("背單詞"); }) }; await Task.WhenAll(tasks); }
補充:
Delay()
FromResult()
並行Linq Parallel LINQ (PLINQ)
僅適用於Linq to Object,主要的措施是:對數據源進行分區,而後多核併發運行(保守模式:若是能不併發就不併發)
核心方法:AsParallel(),在數據源後添加。
try { IEnumerable<int> numbers = Enumerable.Range(0, 1000); var filtered = numbers.AsParallel() //.Where(n => n % 11 == 0) .Where(n => 8 % (n > 100 ? n : 0) == 0) ; filtered.ForAll(f => Console.WriteLine(f)); } catch (AggregateException ae) { ae.Handle(e => { Console.WriteLine(e); return true; }); }
ForAll():一樣能夠併發執行
仍然是AggregateException異常
最佳實踐
使用異步/並行的反作用(side effect):
簡單理解:
老是最後考慮異步/並行:(我的建議)