在.net里面異步編程模型由來已久,相信大家也知道Begin/End異步模式和事件異步模式,在task出現以后,這些東西都可以被task包裝
起來,可能有人會問,這樣做有什么好處,下面一一道來。
一: Begin/End模式
1: 委托
在執行委托方法的時候,我們常常會看到一個Invoke,同時也有一對你或許不常使用的BeginInvoke,EndInvoke方法對,當然Invoke方法
是阻塞主線程,而BeginInvoke則是另開一個線程。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var func = new Func<string, string>(i => { return i + "i can fly"; }); 6 7 var state = func.BeginInvoke("yes,", Callback, func); 8 9 Console.Read(); 10 } 11 12 static void Callback(IAsyncResult async) 13 { 14 var result = async.AsyncState as Func<string, string>; 15 16 Console.WriteLine(result.EndInvoke(async)); 17 } 18 }
下面我們用task包裝一下
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var func = new Func<string, string>(i => 6 { 7 return i + "i can fly"; 8 }); 9 10 Task<string>.Factory.FromAsync(func.BeginInvoke, func.EndInvoke, "yes,", null).ContinueWith 11 (i => 12 { 13 Console.WriteLine(i.Result); 14 }); 15 16 Console.Read(); 17 } 18 }
可以看出,task只要一句就搞定,體現了task的第一個優點:簡潔。
2:流
我們發現在Stream抽象類中提供了這樣兩對BeginRead/EndRead,BeginWrite/EndWrite(異步讀寫)的方法,這樣它的n多繼承類都可以
實現異步讀寫,下面舉個繼承類FileStream的例子。
1 static void Main(string[] args) 2 { 3 var path = "C://1.txt"; 4 5 FileStream fs = new FileStream(path, FileMode.Open); 6 7 FileInfo info = new FileInfo(path); 8 9 byte[] b = new byte[info.Length]; 10 11 var asycState = fs.BeginRead(b, 0, b.Length, (result) => 12 { 13 var file = result.AsyncState as FileStream; 14 15 Console.WriteLine("文件內容:{0}", Encoding.Default.GetString(b)); 16 17 file.Close(); 18 19 }, fs); 20 21 Console.WriteLine("我是主線程,我不會被阻塞!"); 22 23 Console.Read(); 24 }
我們用task包裝一下
1 static void Main(string[] args) 2 { 3 var path = "C://1.txt"; 4 5 FileStream fs = new FileStream(path, FileMode.Open); 6 7 FileInfo info = new FileInfo(path); 8 9 byte[] b = new byte[info.Length]; 10 11 Task<int>.Factory.FromAsync(fs.BeginRead, fs.EndRead, b, 0, b.Length, null, TaskCreationOptions.None) 12 .ContinueWith 13 (i => 14 { 15 Console.WriteLine("文件內容:{0}", Encoding.Default.GetString(b)); 16 }); 17 18 Console.WriteLine("我是主線程,我不會被阻塞!"); 19 20 Console.Read(); 21 }
其實看到這里,我們並沒有發現task還有其他的什么優點,但是深入的想一下其實並不是這么回事,task能夠游刃於線程並發和同步,而原始的異步
編程要實現線程同步還是比較麻煩的。
假如現在有這樣的一個需求,我們需要從3個txt文件中讀取字符,然后進行倒序,前提是不能阻塞主線程。如果不用task的話我可能會用工作線程
去監視一個bool變量來判斷文件是否全部讀取完畢,然后再進行倒序,我也說了,相對task來說還是比較麻煩的,這里我就用task來實現。
1 class Program 2 { 3 static byte[] b; 4 5 static void Main() 6 { 7 string[] array = { "C://1.txt", "C://2.txt", "C://3.txt" }; 8 9 List<Task<string>> taskList = new List<Task<string>>(3); 10 11 foreach (var item in array) 12 { 13 taskList.Add(ReadAsyc(item)); 14 } 15 16 Task.Factory.ContinueWhenAll(taskList.ToArray(), i => 17 { 18 string result = string.Empty; 19 20 //獲取各個task返回的結果 21 foreach (var item in i) 22 { 23 result += item.Result; 24 } 25 26 //倒序 27 String content = new String(result.OrderByDescending(j => j).ToArray()); 28 29 Console.WriteLine("倒序結果:"+content); 30 }); 31 32 Console.WriteLine("我是主線程,我不會被阻塞"); 33 34 Console.ReadKey(); 35 } 36 37 //異步讀取 38 static Task<string> ReadAsyc(string path) 39 { 40 FileInfo info = new FileInfo(path); 41 42 byte[] b = new byte[info.Length]; 43 44 FileStream fs = new FileStream(path, FileMode.Open); 45 46 Task<int> task = Task<int>.Factory.FromAsync(fs.BeginRead, fs.EndRead, b, 0, b.Length, null, TaskCreationOptions.None); 47 48 //返回當前task的執行結果 49 return task.ContinueWith(i => 50 { 51 return i.Result > 0 ? Encoding.Default.GetString(b) : string.Empty; 52 }, TaskContinuationOptions.ExecuteSynchronously); 53 } 54 }
可以看出,task的第二個優點就是:靈活性。
這里可能就有人要問了,能不能用開多個線程用read以同步的形式讀取,變相的實現文件異步讀取,或許我們可能常聽說程序優化后,最后出現的
瓶頸在IO上面,是的,IO是比較耗費資源的,要命的是如果我們開的是工作線程走IO讀取文件,那么該線程就會一直處於等待狀態,不會再接收任
何的外來請求,直到線程讀取到文件為止,那么我們能不能用更少的線程來應對更多的IO操作呢?答案肯定是可以的,這里就設計到了”異步IO“的
概念,具體內容可以參照百科:http://baike.baidu.com/view/1865389.htm ,有幸的是beginXXX,endXXX完美的封裝了“異步IO”。
二:事件模式
這個模式常以XXXCompleted的形式結尾,我們在文件下載這一塊會經常遇到,這里我也舉個例子。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 WebClient client = new WebClient(); 6 7 client.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(client_DownloadFileCompleted); 8 9 client.DownloadFileAsync(new Uri("http://imgsrc.baidu.com/baike/abpic/item/6a600c338744ebf844a0bc74d9f9d72a6159a7ac.jpg"), 10 "1.jpg", "圖片下完了,你懂的!"); 11 12 Console.WriteLine("我是主線程,我不會被阻塞!"); 13 Console.Read(); 14 } 15 16 static void client_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e) 17 { 18 Console.WriteLine("\n" + e.UserState); 19 } 20 }
先前也說了,task是非常靈活的,那么針對這種異步模型,我們該如何封裝成task來使用,幸好framework中提供了TaskCompletionSource來幫助
我們快速實現。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.IO; 6 using System.Threading.Tasks; 7 using System.Net; 8 using System.ComponentModel; 9 10 namespace ConsoleApplication4 11 { 12 class Program 13 { 14 static void Main() 15 { 16 var downloadTask = DownLoadFileInTask( 17 new Uri(@"http://www.7720mm.cn/uploadfile/2010/1120/20101120073035736.jpg") 18 , "C://1.jpg"); 19 20 downloadTask.ContinueWith(i => 21 { 22 Console.WriteLine("圖片:" + i.Result + "下載完畢!"); 23 }); 24 25 Console.WriteLine("我是主線程,我不會被阻塞!"); 26 27 Console.Read(); 28 } 29 30 static Task<string> DownLoadFileInTask(Uri address, string saveFile) 31 { 32 var wc = new WebClient(); 33 34 var tcs = new TaskCompletionSource<string>(address); 35 36 //處理異步操作的一個委托 37 AsyncCompletedEventHandler handler = null; 38 39 handler = (sender, e) => 40 { 41 if (e.Error != null) 42 { 43 tcs.TrySetException(e.Error); 44 } 45 else 46 { 47 if (e.Cancelled) 48 { 49 tcs.TrySetCanceled(); 50 } 51 else 52 { 53 tcs.TrySetResult(saveFile); 54 } 55 } 56 57 wc.DownloadFileCompleted -= handler; 58 }; 59 60 //我們將下載事件與我們自定義的handler進行了關聯 61 wc.DownloadFileCompleted += handler; 62 63 try 64 { 65 wc.DownloadFileAsync(address, saveFile); 66 } 67 catch (Exception ex) 68 { 69 wc.DownloadFileCompleted -= handler; 70 71 tcs.TrySetException(ex); 72 } 73 74 return tcs.Task; 75 } 76 } 77 }