1.基本概念 多線程與非同步是兩個不同概念,之所以把這兩個放在一起學習,是因為這兩者雖然有區別,但也有一定聯繫。 多線程是一個技術概念,相對於單線程而言,多線程是多個單線程同時處理邏輯。例如,假如說一個人把水從A地提到B點可看作是單線程,那麼如果兩個人同時去做事(可以是相同的一件事,也可以是不同的一件 ...
1.基本概念
多線程與非同步是兩個不同概念,之所以把這兩個放在一起學習,是因為這兩者雖然有區別,但也有一定聯繫。
多線程是一個技術概念,相對於單線程而言,多線程是多個單線程同時處理邏輯。例如,假如說一個人把水從A地提到B點可看作是單線程,那麼如果兩個人同時去做事(可以是相同的一件事,也可以是不同的一件事)就可以看作是兩個線程。
非同步:記得讀書時學過一篇課文叫《統籌方法》,裡面講述煮茶喝的過程,如下:
比如,想泡壺茶喝。當時的情況是:開水沒有;水壺要洗,茶壺、茶杯要洗;火已生了,茶葉也有了。怎麼辦?
辦法甲:洗好水壺,灌上涼水,放在火上;在等待水開的時間里,洗茶壺、洗茶杯、拿茶葉;等水開了,泡茶喝。
辦法乙:先做好一些準備工作,洗水壺,洗茶壺茶杯,拿茶葉;一切就緒,灌水燒水;坐待水開了,泡茶喝。
那麼辦法甲就是非同步方法,辦法乙就是同步方法。利用多線程就可以實現辦法甲:如用A線程做“洗好水壺,灌上涼水,放在火上”,另B線程做“洗茶壺、洗茶杯、拿茶葉”,併在B線程中等待A線程執行完畢(即水開),再繼續做“泡茶喝”。
上面是示例講法,所謂的“等待”翻譯成技術語言就是“阻塞”,非同步與同步的區別就是看一事件中是否有阻塞,等待另一件事情的處理結果。從示例中也可以看到,非同步中用到了多線程,但非同步和多線程顯然是兩個不同的概念。
結論:非同步是相對同步來說的一個目的,而多線程是實現這個目的一種技術方法。
2.Task的運用
Task是.NET Framework 4.5加入的概念,之前實現多線程是利用Thread類,在此只對Task進行學習,在實際編碼中基本用Task,因為它比Thread更易理解,更易運用,更安全可靠。
下麵是兩者差異的一個總結:
1.task與thread對比,task相當於應用層,thread更底層,但二者是不一樣的,沒有隸屬關係
2.task是線上程池上創建,是後臺線程(主線程不會等其完成);Thread是單個線程,預設是前臺線程
3.task可以直接獲取返回值,thread不能直接從方法返回結果(可以使用變數來獲取結果)
4.使用task.ContinueWith()方法可以繼續執行其他任務。線程中無連續性,當線程完成工作時,不能告訴線程開始其他操作。 儘管可以使用Join()等待線程完成,但是這會阻塞主線程
5.task藉助CancellationTokeSource類可以支持任務中的取消,當thread處於運行中時,無法取消它
6.task能方便捕捉到運行中的異常,thread在父方法中無法捕捉到異常
下麵用示例代碼來展示Task實現非同步的基本運用:
public static void Main() { // Start the HandleFile method. Task<int> task = HandleFileAsync();//可以看作是一個耗時任務 // Control returns here before HandleFileAsync returns. // ... Prompt the user. Console.WriteLine("Please wait patiently " + "while I do something important."); // Do something at the same time as the file is being read. string line = Console.ReadLine(); Console.WriteLine("You entered (asynchronous logic): " + line); // Wait for the HandleFile task to complete. // ... Display its results. task.Wait();//有調用.result時,這裡可以省略。在沒有調用.result時,一定要.wait()下,這個話題涉及到當task運行出現異常時:為什麼要調用Wait或者Result?
或者一直不查詢Task的Exception屬性?你的代碼就永遠註意不到這個異常的發生,如果不能捕捉到這個異常,垃圾回收時,
拋出AggregateException,進程就會立即終止,這就是“牽一發動全身”,莫名其妙程式就自己關掉了 var x = task.Result;//其實在用Result的時候,內部會調用Wait Console.WriteLine("Count: " + x); Console.WriteLine("[DONE]"); Console.ReadLine(); } static async Task<int> HandleFileAsync()//async與下文的await是成對出現,否則不能實現真正的非同步,而是同步執行。 { string file = @"C:\enable1.txt";//在這個文檔中輸入幾個字元和輸入1MB字元,最終輸出結果會有不同,能很好的展示非同步調用方式 Console.WriteLine("HandleFile enter"); int count = 0; // Read in the specified file. // ... Use async StreamReader method. using (StreamReader reader = new StreamReader(file)) { string v = await reader.ReadToEndAsync(); //string v = reader.ReadToEnd(); // ... Process the file data somehow. count += v.Length; // ... A slow-running computation. // Dummy code. for (int i = 0; i < 10000; i++) { int x = v.GetHashCode(); if (x == 0) { count--; } } } Console.WriteLine("HandleFile exit"); return count; }
上面是task的基本運用,看看註釋瞭解一些註意事項。下麵是對ContinueWith,即連續任務的一個運用,如下:
static void Main() { // Call async method 10 times. for (int i = 0; i < 10; i++) { Run2Methods(i); } // The calls are all asynchronous, so they can end at any time. Console.ReadLine(); } static async void Run2Methods(int count) { // Run a Task that calls a method, then calls another method with ContinueWith. int result = await Task.Run(() => GetSum(count)) .ContinueWith(task => MultiplyNegative1(task)); Console.WriteLine("Run2Methods result: " + result); } static int GetSum(int count) { // This method is called first, and returns an int. int sum = 0; for (int z = 0; z < count; z++) { sum += (int)Math.Pow(z, 2); } return sum; } static int MultiplyNegative1(Task<int> task) { // This method is called second, and returns a negative int. return task.Result * -1; }
下麵是如果想取消任務時CancellationTokenSource類的運用:
static void Main(string[] args) { using (var cts = new CancellationTokenSource()) { Task task = new Task(() => { LongRunningTask(cts.Token); }); task.Start(); Console.WriteLine("Operation Performing..."); if (Console.ReadKey().Key == ConsoleKey.C) { Console.WriteLine("Cancelling.."); cts.Cancel(); } Console.Read(); } } private static void LongRunningTask(CancellationToken token) { for (int i = 0; i < 10000000; i++) { if (token.IsCancellationRequested) { break; } else { Console.WriteLine(i); } } }
以上是Task的基本運用。
3.總結
以上是利用Task、async、await實現非同步的方法,示例三展示的是task實現任務的取消,這裡不是一個非同步方法,只是一個單純的任務取消(可理解為多線程的取消)。Task本質是類似於threadPool的一個線程池,只需把要做的事丟進去,底層線程怎麼分配怎麼運行,編碼時完全不用關心。如果是用thread類,就需要自己去控制線程的啟停銷毀等,控制不好就會“翻車”,而且這種“翻車”會導致出現莫名其妙的結果,甚至程式鎖死,因此在實際編碼中儘量使用task類。