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 }



浙公网安备 33010602011771号