在构建高性能、可伸缩的应用程序时,必定会采用异步操作来提升应用程序性能,改善用户体验,异步操作与线程池结合允许使用很少的线程执行许多的操作。CLR中提供了一种异步操作的模式即异步编程模式。
1.异步编程模型简介
异步编程模式中的方法都是采用BeginXxx方法开始执行异步操作和EndXxx方法结束异步操作。BeginXxx方法都接受一个AsyncCallback委托类型的回调函数和回调函数需要使用的一个object类型的数据对象以及其他的一些操作参数,并且该方法会返回一个实现了IAsyncResult接口类型对象,EndXxx方法都接受一个IAsyncResult接口的类型参数。AsyncCallback委托类型定义如下:
public delegate void AsyncCallback(IAsyncResult ar);
IAsyncResult接口包含一个获取用户定义的数据以及包含异步操作的信息的属性AsyncState, 获取用于等待异步操作完成的 System.Threading.WaitHandle对象引用,获取一个指示异步操作是否同步完成的值CompletedSynchronously以及指示异步操作是否已完成的值IsCompleted。
采用异步编程时,调用了BeginXxx方法后CLR内部会维护一个BeginXxx方法返回的IAsyncResult类型的实例引用并在异步操作完成后调用用户的回调方法,同时向回调方法传递内部维护的IAsyncResult实例的引用。在回调方法中总是需要调用EndXxx方法一次并且只能调用一次,并传递BeginXxx方法调用时所维护的一个IAsyncResult类型的实例引用,调用BeginXxx方法和EndXxx方法的对象必须为相同对象,如果类型不相同就会抛出InvalidOperationException异常(所提供的IAsyncResult对象与此委托不相同。)。初始化异步操作时CLR会分配一些内部资源,操作完成以后CLR会保留这些资源直到调用EndXxx方法时,它会访问这些内部资源并释放它们,如果不调用EndXxx方法就无法获取到异步操作是否完成以及操作中是否抛出了异常,同时也会浪费资源。
在FCL中对许多的类型都提供了异步操作,尤其是与I/O操作相关的类都提供了异步编程模式,如下一些类型都提供了异步操作的功能:
- 所有派生自 System.IO.Stream 的类并与硬件通信的类型(如:FileStream,NetWorkStream等)都提供了BeginRead和BeginWrite方法来执行异步操作。
- 所有派生自 System.Net.WebRequest 的类(如:HttpWebRequest,FtpWebRequest等)都提供了BeginGetRequstStream和BeginGetResponse方法。
- System.Data.SqlClient.SqlCommand 类提供了BeginExecuteNonQuery、BeginExecuteReader和BeginExecuteXmlReader方法。
- System.Net.Sockets.Socket 类提供了BeginAccept、BeginConnect、BeginDisconnect、BeginReceive、BeginReceiveFrom、BeginReceiveMessageFrom、BeginSend、BeginSendFile和BeginSendTo方法。
在创建委托时编译器会自动为委托类型增加APM的方法BeginInvoke和EndInvoke方法。
2.异步的I/O操作
在应用程序中,一般都存在大量的自定义配置或数据文件,比如:用XML文件存储的用户数据或配置信息,用Json格式存储的数据文件等。在操作这些文件的时候为了不阻塞工作者线程提高相应速度都会采用异步的读写方式来读取或更新文件。如下示例表示异步的写入和读取Json格式数据:
1: private readonly static string filePath = "http://www.cnblogs.com/JsonData/JsonConfig.json";
2: private static void AsyncWriteAndReadData()
3: {
4: //异步写入文件
5: FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, 1024, FileOptions.Asynchronous);
6: Dictionary<string, object> config = new Dictionary<string, object>() {
7: {"Name","APMJsonData"},
8: {"Time",DateTime.Now.ToString()},
9: {"Person","Stone"}
10: };
11: System.Web.Script.Serialization.JavaScriptSerializer serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
12: string json = serializer.Serialize(config);
13: byte[] bt = Encoding.UTF8.GetBytes(json);
14: fs.BeginWrite(bt, 0, bt.Length, state =>
15: {
16: FileStream fsEnd = state.AsyncState as FileStream;
17: if (fsEnd != null)
18: {
19: fsEnd.EndWrite(state);
20: fsEnd.Dispose();
21: Console.WriteLine("异步写入数据成功!");
22: }
23:
24: }, fs);
25: //阻塞线程,文件资源释放以后再读取数据
26: Thread.Sleep(2000);
27: //异步的读取文件
28: fs = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.Read, 1024, FileOptions.Asynchronous);
29: fs.BeginRead(bt, 0, bt.Length, state =>
30: {
31: FileStream fsEnd = state.AsyncState as FileStream;
32: if (fsEnd != null)
33: {
34: fsEnd.EndRead(state);
35: fsEnd.Dispose();
36: json = Encoding.UTF8.GetString(bt);
37: var configInfo = serializer.Deserializestring, object>>(json.Replace("\0", ""));
38: foreach (var item in configInfo)
39: {
40: Console.WriteLine("{0}:{1}", item.Key, item.Value);
41: }
42: }
43: }, fs);
44: }
如上代码所示在异步写入文件或异步读取文件时使用的BeginXxx方法并在回调方法中调用了EndXxx方法。使用FileStream类异步操作文件时需要注意必须知道文件的操作方法:文件是否可用于异步读取和写入,即创建FileStream实例时需要采用带有FileOptions类型参数的重载构造函数,指定FileOptions.Asynchronous值时操作文件才会以异步的方方式读取和写入。也可以使用如下的构造函数来创建实例:
public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync);
public delegate void SendOrPostCallback(object state);
1: private void button1_Click(object sender, EventArgs e)
2: {
3: //获得当前线程的同步上下文
4: SynchronizationContext syncContext = SynchronizationContext.Current;
5: string url =this.textBox1.Text;
6: //创建WebRequest对象并异步请求资源
7: WebRequest request = HttpWebRequest.Create(url);
8: request.BeginGetResponse(state =>
9: {
10: WebRequest webReq = state.AsyncState as WebRequest;
11: using (WebResponse response = webReq.EndGetResponse(state))
12: {
13: using (var stream = response.GetResponseStream())
14: {
15: if (response.ContentLength != -1)
16: {
17: byte[] bt = new byte[response.ContentLength];
18: stream.Read(bt, 0, bt.Length);
19: string content = Encoding.Default.GetString(bt);
20: //使用同步上下文的Post方法同步更新UI
21: syncContext.Post(obj =>
22: {
23: this.textBox2.Text = obj.ToString();
24: }, content);
25: }
26: }
27: }
28: }, request);
29: }
1: private static void AsyncMethod()
2: {
3: //定义一个与方法声明相同的委托来异步执行方法
4: Func<int> apm = () => {
5: //耗时的计算任务
6: Thread.Sleep(2000);
7: int sum = 0;
8: for (int i = 0; i < 100; i++)
9: {
10: sum += i;
11: }
12: return sum;
13: };
14:
15: apm.BeginInvoke(state => {
16: int sum= apm.EndInvoke(state);
17: Console.WriteLine("使用委托异步执行方法,结果为:{0}",sum.ToString());
18: },null);
19: }
4.将APM转换为Task来执行异步操作
在TaskFactory类中提供了一个FromAsync方法,它包含4个参数,异步操作的开始方法BeginXxx、异步操作的结束方法EndXxx、Object类型的状态数据和TaskCreationOptions 类型的参数。定义如下:
public Task FromAsync(Funcobject, IAsyncResult> beginMethod, Action endMethod, object state, TaskCreationOptions creationOptions);
该方法返回一个Task类型的实例,通过该方法可以把一个APM转换为任务来执行。
Task类实现了IAsyncResult接口,Task的AsyncState属性就是IAsyncResult接口的AsyncState属性,通过该属性就可以获得传给TaskFactory类FromAsync方法的数据对象(state的值)。
将异步I/O转换为Task来执行示例:
1: private void APMToTask() {
2: //获得当前线程的同步上下文
3: SynchronizationContext syncContext = SynchronizationContext.Current;
4: string url = this.textBox1.Text;
5: //创建WebRequest对象并异步请求资源
6: WebRequest request = HttpWebRequest.Create(url);
7: //通过Task类的Factory属性获得TaskFactory实例,并调用FromAsync方法将异步I/O操作转换为任务执行
8: var task= Task.Factory.FromAsync(request.BeginGetResponse,request.EndGetResponse,request,TaskCreationOptions.None);
9: task.ContinueWith(t => {
10: //任务完成以后启动延续任务,并获得异步请求数据。
11: using (WebResponse response =t.Result)
12: {
13: using (var stream = response.GetResponseStream())
14: {
15: if (response.ContentLength != -1)
16: {
17: byte[] bt = new byte[response.ContentLength];
18: stream.Read(bt, 0, bt.Length);
19: string content = Encoding.Default.GetString(bt);
20: //使用同步上下文的Post方法同步更新UI
21: syncContext.Post(obj =>
22: {
23: this.textBox2.Text = obj.ToString();
24: }, content);
25: }
26: }
27: }
28: });
29: }
本文简单的介绍了异步编程模型(APM)的知识,这种模式非常适合与I/O相关的操作,对于开发高性能、可伸缩的应用程序是非常有用的技术,比如异步获取网络资源,异步操作文件等。