在.net里面异步编程模型由来已久,相信大家也知道Begin/End异步模式和事件异步模式,在task出现以后,这些东西都可以被task包装
起来,可能有人会问,这样做有什么好处,下面一一道来。
一: Begin/End模式
1: 委托
在执行委托方法的时候,我们常常会看到一个Invoke,同时也有一对你或许不常使用的BeginInvoke,EndInvoke方法对,当然Invoke方法
是阻塞主线程,而BeginInvoke则是另开一个线程。
class Program
{
static void Main(string[] args)
{
var func = new Func<string, string>(i => { return i + "i can fly"; });
var state = func.BeginInvoke("yes,", Callback, func);
Console.Read();
}
static void Callback(IAsyncResult async)
{
var result = async.AsyncState as Func<string, string>;
Console.WriteLine(result.EndInvoke(async));
}
}
下面我们用task包装一下
class Program
{
static void Main(string[] args)
{
var func = new Func<string, string>(i =>
{
return i + "i can fly";
});
Task<string>.Factory.FromAsync(func.BeginInvoke, func.EndInvoke, "yes,", null).ContinueWith
(i =>
{
Console.WriteLine(i.Result);
});
Console.Read();
}
}
可以看出,task只要一句就搞定,体现了task的第一个优点:简洁。
2:流
我们发现在Stream抽象类中提供了这样两对BeginRead/EndRead,BeginWrite/EndWrite(异步读写)的方法,这样它的n多继承类都可以
实现异步读写,下面举个继承类FileStream的例子。
static void Main(string[] args)
{
var path = "C://1.txt";
FileStream fs = new FileStream(path, FileMode.Open);
FileInfo info = new FileInfo(path);
byte[] b = new byte[info.Length];
var asycState = fs.BeginRead(b, 0, b.Length, (result) =>
{
var file = result.AsyncState as FileStream;
Console.WriteLine("文件内容:{0}", Encoding.Default.GetString(b));
file.Close();
}, fs);
Console.WriteLine("我是主线程,我不会被阻塞!");
Console.Read();
}
我们用task包装一下
static void Main(string[] args)
{
var path = "C://1.txt";
FileStream fs = new FileStream(path, FileMode.Open);
FileInfo info = new FileInfo(path);
byte[] b = new byte[info.Length];
Task<int>.Factory.FromAsync(fs.BeginRead, fs.EndRead, b, 0, b.Length, null, TaskCreationOptions.None)
.ContinueWith
(i =>
{
Console.WriteLine("文件内容:{0}", Encoding.Default.GetString(b));
});
Console.WriteLine("我是主线程,我不会被阻塞!");
Console.Read();
}
其实看到这里,我们并没有发现task还有其他的什么优点,但是深入的想一下其实并不是这么回事,task能够游刃于线程并发和同步,而原始的异步
编程要实现线程同步还是比较麻烦的。
假如现在有这样的一个需求,我们需要从3个txt文件中读取字符,然后进行倒序,前提是不能阻塞主线程。如果不用task的话我可能会用工作线程
去监视一个bool变量来判断文件是否全部读取完毕,然后再进行倒序,我也说了,相对task来说还是比较麻烦的,这里我就用task来实现。
class Program
{
static byte[] b;
static void Main()
{
string[] array = { "C://1.txt", "C://2.txt", "C://3.txt" };
List<Task<string>> taskList = new List<Task<string>>(3);
foreach (var item in array)
{
taskList.Add(ReadAsyc(item));
}
Task.Factory.ContinueWhenAll(taskList.ToArray(), i =>
{
string result = string.Empty;
//获取各个task返回的结果
foreach (var item in i)
{
result += item.Result;
}
//倒序
String content = new String(result.OrderByDescending(j => j).ToArray());
Console.WriteLine("倒序结果:"+content);
});
Console.WriteLine("我是主线程,我不会被阻塞");
Console.ReadKey();
}
//异步读取
static Task<string> ReadAsyc(string path)
{
FileInfo info = new FileInfo(path);
byte[] b = new byte[info.Length];
FileStream fs = new FileStream(path, FileMode.Open);
Task<int> task = Task<int>.Factory.FromAsync(fs.BeginRead, fs.EndRead, b, 0, b.Length, null, TaskCreationOptions.None);
//返回当前task的执行结果
return task.ContinueWith(i =>
{
return i.Result > 0 ? Encoding.Default.GetString(b) : string.Empty;
}, TaskContinuationOptions.ExecuteSynchronously);
}
}
可以看出,task的第二个优点就是:灵活性。
这里可能就有人要问了,能不能用开多个线程用read以同步的形式读取,变相的实现文件异步读取,或许我们可能常听说程序优化后,最后出现的
瓶颈在IO上面,是的,IO是比较耗费资源的,要命的是如果我们开的是工作线程走IO读取文件,那么该线程就会一直处于等待状态,不会再接收任
何的外来请求,直到线程读取到文件为止,那么我们能不能用更少的线程来应对更多的IO操作呢?答案肯定是可以的,这里就设计到了”异步IO“的
概念,具体内容可以参照百科:http://baike.baidu.com/view/1865389.htm ,有幸的是beginXXX,endXXX完美的封装了“异步IO”。
二:事件模式
这个模式常以XXXCompleted的形式结尾,我们在文件下载这一块会经常遇到,这里我也举个例子。
class Program
{
static void Main(string[] args)
{
WebClient client = new WebClient();
client.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(client_DownloadFileCompleted);
client.DownloadFileAsync(new Uri("http://imgsrc.baidu.com/baike/abpic/item/6a600c338744ebf844a0bc74d9f9d72a6159a7ac.jpg"),
"1.jpg", "图片下完了,你懂的!");
Console.WriteLine("我是主线程,我不会被阻塞!");
Console.Read();
}
static void client_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
Console.WriteLine("\n" + e.UserState);
}
}
先前也说了,task是非常灵活的,那么针对这种异步模型,我们该如何封装成task来使用,幸好framework中提供了TaskCompletionSource来帮助
我们快速实现。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using System.Net;
using System.ComponentModel;
namespace ConsoleApplication4
{
class Program
{
static void Main()
{
var downloadTask = DownLoadFileInTask(
new Uri(@"http://www.7720mm.cn/uploadfile/2010/1120/20101120073035736.jpg")
, "C://1.jpg");
downloadTask.ContinueWith(i =>
{
Console.WriteLine("图片:" + i.Result + "下载完毕!");
});
Console.WriteLine("我是主线程,我不会被阻塞!");
Console.Read();
}
static Task<string> DownLoadFileInTask(Uri address, string saveFile)
{
var wc = new WebClient();
var tcs = new TaskCompletionSource<string>(address);
//处理异步操作的一个委托
AsyncCompletedEventHandler handler = null;
handler = (sender, e) =>
{
if (e.Error != null)
{
tcs.TrySetException(e.Error);
}
else
{
if (e.Cancelled)
{
tcs.TrySetCanceled();
}
else
{
tcs.TrySetResult(saveFile);
}
}
wc.DownloadFileCompleted -= handler;
};
//我们将下载事件与我们自定义的handler进行了关联
wc.DownloadFileCompleted += handler;
try
{
wc.DownloadFileAsync(address, saveFile);
}
catch (Exception ex)
{
wc.DownloadFileCompleted -= handler;
tcs.TrySetException(ex);
}
return tcs.Task;
}
}
}