8天玩轉並行開發——第六天 異步編程模型


     

      在.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 }


免責聲明!

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



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