線程也瘋狂-----異步編程


前言

本節主要介紹異步編程中Task、Async和Await的基礎知識。

什么是異步?

異步處理不用阻塞當前線程來等待處理完成,而是允許后續操作,直至其它線程將處理完成,並回調通知此線程。

異步和多線程

相同點:避免調用線程阻塞,從而提高軟件的可響應性。

不同點:

異步操作無須額外的線程負擔,並且使用回調的方式進行處理,在設計良好的情況下,處理函數可以不必使用共享變量(即使無法完全不用,最起碼可以減少 共享變量的數量),減少了死鎖的可能。C#5.0 .NET4.5 以后關鍵字Async和Await的使用,使得異步編程變得異常簡單。

多線程中的處理程序依然是順序執行,但是多線程的缺點也同樣明顯,線程的使用(濫用)會給系統帶來上下文切換的額外負擔。並且線程間的共享變量可能造成死鎖的出現。

異步應用場景及原理

異步主要應用於IO操作,數據庫訪問,磁盤操作,Socket訪問、HTTP/TCP網絡通信 

原因:對於IO操作並不需要CPU進行過多的計算,這些數據主要通過磁盤進行處理,如果進行同步通信無法結束,需要創建更多的線程資源,線程的數據上下文頻繁的切換也是對資源的浪費,針對IO操作不需要單獨的分配一個線程來處理。

舉例說明:

操作:服務器接收HTTP請求對數據庫進行操作然后返回

同步處理請求的線程會被阻塞,異步處理請求的線程不會阻塞。

 

任務

  在使用任務之前,針對線程的調用大多都用線程池提供的靜態方法QueueUserWorkItem,但是這個函數有很多的限制,其中最大的問題就是沒有內部機制可以讓開發者知道操作在什么時候完成,也沒有機制在操作完成時獲取返回值,微軟為了解決這個問題引入了任務的概念。

 首先構造一個Task<TResult>對象,並為TResult傳遞返回值,開始任務之后等待它並回去結果,示例代碼:

 1 static void Main(string[] args)
 2         {
 3             Console.WriteLine("開始進行計算");
 4            // ThreadPool.QueueUserWorkItem(Sum, 10);
 5             Task<int> task = new Task<int>(Sum, 100);
 6             task.Start();
 7             //顯示等待獲取結果
 8             task.Wait();
 9             //調用Result時,等待返回結果
10             Console.WriteLine("程序結果為 Sum = {0}",task.Result);
11             Console.WriteLine("程序結束");
12             Console.ReadLine();
13         }
14 
15         public static int Sum(object i)
16         {
17             var sum = 0;
18             for (var j = 0; j <= (int) i; j++)
19             {
20                 Console.Write("{0} + ",sum);
21                 sum += j;
22             }
23             Console.WriteLine( " = {0}",sum);
24             return sum;
25         }

除了wait等待單個任務外,task還提供了等待多個任務,WaitAny和WaitAll,它阻止調用線程,直到數組中所有的Task對象完成。

取消任務

任務的取消同樣使用的是.NET Framework的標准取消操作模式,首先需要創建一個CancellationTokenSource對象,然后在函數中加入參數CancellationToken,將CancellationTokenSource的Token傳遞給方法,然后調用IsCancellationRequested獲取是否已經取消該值進行判斷。

 1 static void Main(string[] args)
 2         {
 3             Console.WriteLine("開始進行計算");
 4             // ThreadPool.QueueUserWorkItem(Sum, 10);
 5             var  ctx = new CancellationTokenSource();
 6             var task = new Task<int>(() => Sum(ctx.Token, 100000));
 7             task.Start();
 8             //顯示等待獲取結果
 9             //task.Wait(ctx.Token);
10             Thread.Sleep(1000);
11             ctx.Cancel();
12             //調用Result時,等待返回結果
13             Console.WriteLine("程序結果為 Sum = {0}", task.Result);
14             Console.WriteLine("程序結束");
15             Console.ReadLine();
16         }
17 
18         public static int Sum(CancellationToken cts, object i)
19         {
20             var sum = 0;        
21             for (var j = 0; j <= (int)i; j++)
22             {
23                 if (cts.IsCancellationRequested) return sum;
24                 Thread.Sleep(50);
25                 Console.Write("{0} + ", sum);
26                 sum += j;
27             }
28             Console.WriteLine(" = {0}", sum);
29             return sum;
30         }

任務完成后自動啟動新任務

實際的開發應用中,經常出現一次任務完成后立刻啟動另外一個任務,並且不能夠使線程阻塞,在任務尚未完成時調用result會使程序阻塞,無法查看任務的執行進度,TASK提供了一個方法ContinueWith,它不會阻塞任何線程,當第一個任務完成時,會立即啟動第二個任務。

 1 static void Main(string[] args)
 2         {
 3             Console.WriteLine("開始進行計算");
 4             // ThreadPool.QueueUserWorkItem(Sum, 10);
 5             var  ctx = new CancellationTokenSource();
 6             var task = new Task<int>(() => Sum(ctx.Token, 100000));
 7             task.Start();
 8             var cwt = task.ContinueWith(p =>
 9             {
10                 Console.WriteLine("task result ={0} ",task.Result);
11             });
12             //顯示等待獲取結果
13             //task.Wait(ctx.Token);
14             Thread.Sleep(1000);
15             ctx.Cancel();
16             //調用Result時,等待返回結果
17             Console.WriteLine("程序結果為 Sum = {0}", task.Result);
18             Console.WriteLine("程序結束");
19             Console.ReadLine();
20         }
21 
22         public static int Sum(CancellationToken cts, object i)
23         {
24             var sum = 0;        
25             for (var j = 0; j <= (int)i; j++)
26             {
27                 if (cts.IsCancellationRequested) return sum;
28                 Thread.Sleep(50);
29                 Console.Write("{0} + ", sum);
30                 sum += j;
31             }
32             Console.WriteLine(" = {0}", sum);
33             return sum;
34         }

Async&Await 簡單使用

使用Async&Await的主要目的是方便進行異步操作,因為.net 4.0 以前進行異步操作時比較復雜的,主要是通過調用微軟提供的異步回調方法進行編程,如果遇到需要自己實現的方法顯得非常頭疼,.net的各個版本都有自己主推的技術,像.NET1.1中的委托,.NET2.0中的泛型,.NET3.0中的Linq,.NET4.0中的Dynimac,.net4.5主推的就是異步編程,大家只需要了解TASK+異步函數就可以實現異步編程。

async:告訴CLR這是一個異步函數。

await:  將Task<TResult>返回值的函數進行異步處理。

 

示例目的:獲取網址JS代碼,並在界面顯示。

 1  private static async Task<string> DownloadStringWithRetries(string uri)
 2         {
 3             using (var client = new HttpClient())
 4             {
 5                 // 第1 次重試前等1 秒,第2 次等2 秒,第3 次等4 秒。
 6                 var nextDelay = TimeSpan.FromSeconds(1);
 7                 for (int i = 0; i != 3; ++i)
 8                 {
 9                     try
10                     {
11                         return await client.GetStringAsync(uri);
12                     }
13                     catch
14                     {
15                     }
16                     await Task.Delay(nextDelay);
17                     nextDelay = nextDelay + nextDelay;
18                 }
19                 // 最后重試一次,以便讓調用者知道出錯信息。
20                 return await client.GetStringAsync(uri);
21             }
22         }
 1  static  void Main(string[] args)
 2         {
 3             Console.WriteLine("獲取百度數據");
 4             ExecuteAsync();
 5             Console.WriteLine("線程結束");
 6             Console.ReadLine();
 7         }
 8 
 9         public static async void ExecuteAsync()
10         {
11            string text = await DownloadStringWithRetries("http://wwww.baidu.com");
12            Console.WriteLine(text);
13         }

運行結果發現,首先獲取百度數據,線程結束,最后顯示HTML代碼,這是因為異步開啟了新的線程,並不會造成線程阻塞。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM