若是一個方法被調用,調用者須要等待該方法被執行完畢以後才能繼續執行,則是同步。html
若是方法被調用後馬上返回,即便該方法是一個耗時操做,也能馬上返回到調用者,調用者不須要等待該方法,則稱之爲異步。編程
異步編程須要用到Task任務函數,不返回值的任務由 System.Threading.Tasks.Task 類表示。返回值的任務由 System.Threading.Tasks.Task<TResult> 類表示,該類從Task 繼承。api
C#提供了基於任務的異步編程方法(TPL),更多資料在《.NET 中的並行編程》https://docs.microsoft.com/zh-cn/dotnet/standard/parallel-programming/多線程
先來個例子看下Task的基本用法。異步
static void Main(string[] args) { var task = new Task(() => { Console.WriteLine($"hello, task的線程ID爲{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine("task start...."); Thread.Sleep(2000); Console.WriteLine("task end...."); }); task.Start(); Console.WriteLine("main start...."); Thread.Sleep(1000); Console.WriteLine("main end...."); Console.ReadLine(); }
運行結果爲:async
經過new方法建立,而後start啓動。異步編程
經過Task.Factory.StartNew建立。函數
經過Task.Run建立。學習
Task task1 = new Task(() => Console.WriteLine($"hello, task 1的線程ID{Thread.CurrentThread.ManagedThreadId}");}); task1.Start(); Task task2 = Task.Run(() => {Console.WriteLine($"hello, task 2的線程ID{Thread.CurrentThread.ManagedThreadId}");}); Task task3 = Task.Factory.StartNew(() => {Console.WriteLine($"hello,task 3的線程ID爲{Thread.CurrentThread.ManagedThreadId}");});
若是要等待任務完成可使用Task.Wait方法,使用WaitAll方法等待全部任務完成。若是等待多個任務可是隻須要任何一個任務完成就能夠執行下一步,則可使用WaitAny。spa
static void Main(string[] args) { var t1 = Task.Run(() => { Thread.Sleep(100); Console.WriteLine($"t1線程ID爲{ Thread.CurrentThread.ManagedThreadId}"); }); var t2 = Task.Run(() => { Thread.Sleep(100); Console.WriteLine($"t2線程ID爲{ Thread.CurrentThread.ManagedThreadId}"); }); // t1.Wait(); var t3 = Task.Run(() => { Console.WriteLine($"t3線程ID爲{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine("t3 start...."); Thread.Sleep(2000); Console.WriteLine("t3 end...."); });
Console.WriteLine("main start...."); Thread.Sleep(1000); Console.WriteLine("main end...."); Console.ReadLine(); }
運行效果:
若是打開t1.Wait(); 以下t1先執行。
若是t1.Wait()改成:Task.WaitAll(t1, t2);輸出以下,先等待t1和t2執行完畢。
若是須要某個任務先執行完再運行主線程,則使用RunSynchronously就能夠同步執行Task任務。下面的例子就是在當前的調度器上同步的執行任務t1。
var t1 = new Task(() =>
{ Thread.Sleep(100); Console.WriteLine($"t1線程ID爲{ Thread.CurrentThread.ManagedThreadId}"); }); t1.RunSynchronously();
Console.WriteLine($"主線程ID爲{ Thread.CurrentThread.ManagedThreadId}");
Console.ReadLine();
四、任務的狀態和返回值
Task<Tresult>獲取異步任務的返回值。
var t1 = Task.Run(() => { Thread.Sleep(100); Console.WriteLine($"t1線程ID爲{ Thread.CurrentThread.ManagedThreadId}"); return "t1 return"; }); Console.WriteLine($"t1 return value {t1.Result}");
Task<Tresult>會阻塞任務直到任務返回,Task.Result給出任務的返回值給調用它的任務,下面例子顯示了Result等待任務完成。
var cts = new CancellationTokenSource(); Task<int> t1 = new Task<int>(() => { try { //while (!cts.IsCancellationRequested) { //cts.Token.ThrowIfCancellationRequested(); Console.WriteLine($"t1 start 線程ID爲{ Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); Console.WriteLine($"t1 end 線程ID爲{ Thread.CurrentThread.ManagedThreadId}"); } return 1; } catch (Exception ex) { Console.WriteLine("---Exception---"); return 0; } }, cts.Token); t1.Start(); Console.WriteLine("---end---{0}", t1.Result);
輸出結果以下,t.Result爲1.
t.Result會致使阻塞直至等待的任務完成返回。若是使用返回值,那麼異步方法中方法簽名返回值爲Task<T>,代碼中的返回值也要爲T。
static void Main(string[] args) { var cts = new CancellationTokenSource(); Task<int> t1 = new Task<int>(() => { try { //while (!cts.IsCancellationRequested) { //cts.Token.ThrowIfCancellationRequested(); Console.WriteLine($"t1 start 線程ID爲{ Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); Console.WriteLine($"t1 end 線程ID爲{ Thread.CurrentThread.ManagedThreadId}"); } return 1; } catch (Exception ex) { Console.WriteLine("---Exception---"); return 0; } }, cts.Token); t1.Start(); // Console.WriteLine("---end---{0}", t1.Result); var t2 = Task.Run(() => { Thread.Sleep(100); Console.WriteLine($"t2線程ID爲{ Thread.CurrentThread.ManagedThreadId}"); }); Console.ReadLine(); } }
輸出爲:
若是打開註釋Console.WriteLine("---end---{0}", t1.Result);則任務阻塞,等待t1執行完,拿到返回值t1.Result以後才繼續執行,輸出爲:
任務出錯或用戶取消了操做,則須要用到任務的取消TaskCanceledException。
cts.Cancel()設置了cts.IsCancellationRequested爲true,
而後cts.Token.ThrowIfCancellationRequested()這一句拋出了異常。
執行結果爲:
若是主線程不延時或延時爲10,則有可能在任務啓動以前就中止了任務,就不會發生任務拋出異常了,因爲系統調度問題,必定狀況下輸出與上面的同樣,大部分狀況輸出爲:
此外,也能夠在task中查詢標誌。while (!cts.IsCancellationRequested),若是取消則不在執行。若是一次取消多個任務,可使用CancellationTokenSource.CreateLinkedTokenSource實現。
幫助文檔:
從開始.NET Framework 4,.NET Framework 使用統一的模型來協做取消異步操做或長時間運行的同步操做涉及兩個對象:
一個CancellationTokenSource對象,它提供取消標記經過其Token屬性,並將取消消息經過調用其Cancel或CancelAfter方法。
一個CancellationToken對象,指示是否請求取消。
用於實現協做取消模型的常規模式是:
實例化 CancellationTokenSource 對象,此對象管理取消通知並將其發送給單個取消標記。
將 CancellationTokenSource.Token 屬性返回的標記傳遞給每一個偵聽取消的任務或線程。
調用CancellationToken.IsCancellationRequested從接收取消標記的操做的方法。 提供有關每一個任務或線程來響應取消請求的機制。 不管你選擇取消操做和徹底操做方式,取決於你的應用程序邏輯。
調用 CancellationTokenSource.Cancel 方法以提供取消通知。 這將設置CancellationToken.IsCancellationRequested到取消標記的每一個副本上的屬性true。
調用Dispose方法在您使用完CancellationTokenSource對象。
Task是基於Thread的,是比較高層級的封裝,Task任務最終仍是須要Thread來執行。
Task比Thread的開銷小,Task默認使用線程池。
Task默認使用後臺線程來執行,Thread默認是前臺線程。
Task使用參數和返回值都比Thread簡單。
Task的多任務調度比Thread靈活,Thead的join須要掛起調用者去執行被調用線程而後返回到主線程。而Task提供了多種Wait函數,能夠等待其餘線程執行。
本文只是學習筆記,水平有限,拋磚迎玉。
一、基於任務的異步編程
二、Task 類
https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.task?view=netframework-4.8
三、Difference Between Task and Thread
https://www.dotnetforall.com/difference-task-and-thread/
四、Task And Thread In C#
https://www.c-sharpcorner.com/article/task-and-thread-in-c-sharp/
五、C#多線程和異步(一)——基本概念和使用方法