一、异步编程模型(APM)

二、基于事件的异步编程模式(EAP)

三、基于任务的异步模式(TAP),推荐使用

四、C# 5.0 新特性——Async和Await使异步编程更简单

一、概念

APM即异步编程模式的简写(Asynchronous Programming Model)。大家在写代码的时候或者查看.NET 的类库的时候肯定会经常看到和使用以BeginXXX和EndXXX类似的方法,其实你在使用这些方法的时候,你就再使用异步编程模型来编写程序。NET Framework很多类也实现了该模式,同时我们也可以自定义类来实现该模式,(也就是在自定义的类中实现返回类型为IAsyncResult接口的BeginXXX方法和EndXXX方法),另外委托类型也定义了BeginInvoke和EndInvoke方法。

下面就具体就拿FileStream类的BeginReadEndRead方法来介绍下下异步编程模型的实现。

BeginXxx方法——开始执行异步操作介绍

当需要读取文件中的内容时,我们通常会采用FileStream的同步方法Read来读取,该同步方法的定义为:

// 从文件流中读取字节块并将该数据写入给定的字节数组中
// array代表把读取的字节块写入的缓存区
// offset代表array的字节偏量,将在此处读取字节
// count 代表最多读取的字节数
public override int Read(byte[] array, int offset, int count )

该同步方法会堵塞执行的线程。可以通过BeginRead方法来实现异步编程,使读取操作不再堵塞UI线程。BeginRead方法代表异步执行Read操作,并返回实现IAsyncResult接口的对象,该对象存储着异步操作的信息,下面就看下BeginRead方法的定义,看看与同步Read的方法区别在哪里的.

// 开始异步读操作
// 前面的3个参数和同步方法代表的意思一样,这里就不说了,可以看到这里多出了2个参数
// userCallback代表当异步IO操作完成时,你希望由一个线程池线程执行的方法,该方法必须匹配AsyncCallback委托
// stateObject代表你希望转发给回调方法的一个对象的引用,在回调方法中,可以查询IAsyncResult接口的AsyncState属性来访问该对象
public override IAsyncResult BeginRead(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject
)

从上面的代码中可以看出异步方法和同步方法的区别,如果你在使用该异步方法时,不希望异步操作完成后调用任何代码,你可以把userCallback参数设置为null。该异步方法子所以不会堵塞UI线程是因为调用该方法后,该方法会立即把控制权返回给调用线程(如果是UI线程来调用该方法时,即返回给UI线程),然而同步却不是这样,同步方法是等该操作完成之后返回读取的内容之后才返回给调用线程,从而导致在操作完成之前调用线程就一直等待状态。

EndXxx方法——结束异步操作介绍

 前面介绍完了BeginXxx方法,我们看到所有BeginXxx方法返回的都是实现了IAsyncResult接口的一个对象,并不是对应的同步方法那样直接得到结果。此时我们需要调用对应的EndXxx方法来结束异步操作,并向该方法传递IAsyncResult对象,EndXxx方法的返回类型就是和同步方法一样的。例如,FileStreamEndRead方法返回一个Int32来代表从文件流中实际读取的字节数。

// 摘要:  等待挂起的异步读取完成。
// asyncResult:对要完成的挂起异步请求的引用。
// 返回结果: 从流中读取的字节数.
public virtual int EndRead(IAsyncResult asyncResult);

对于访问异步操作的结果,APM的首选方式是:
使用 AsyncCallback委托来指定操作完成时要调用的方法,在操作完成后调用的方法中调用EndXxx操作来获得异步操作的结果。

异步编程模型的本质

其实异步编程模型这个模式,就是微软利用委托和线程池帮助我们实现的一个模式(该模式利用一个线程池线程去执行一个操作,在FileStream类BeginRead方法中就是执行一个读取文件操作,该线程池线程会立即将控制权返回给调用线程,此时线程池线程在后台进行这个异步操作;异步操作完成之后,通过回调函数来获取异步操作返回的结果。此时就是利用委托的机制。所以说异步编程模式时利用委托和线程池线程搞出来的模式,包括后面的基于事件的异步编程和基于任务的异步编程,还有C# 5中的async和await关键字,都是利用这委托和线程池搞出来的。他们的本质其实都是一样的,只是后面提出来的使异步编程更加简单罢了。)

二、同步方法举例:

存储请求的状态类:

// 这个类存储请求的状态
public class RequestState
{
public int BufferSize = 1024;
public string savepath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\TAP.docx";
public byte[] BufferRead;
public HttpWebRequest request;
public HttpWebResponse response;
public Stream streamResponse; public FileStream filestream; public RequestState()
{
BufferRead = new byte[BufferSize];
request = null;
streamResponse = null;
if (File.Exists(savepath))
{
File.Delete(savepath);
} filestream = new FileStream(savepath, FileMode.OpenOrCreate);
}
}

主程序:

private static void Main(string[] args)
{
string downUrl = "http://download.microsoft.com/download/5/B/9/5B924336-AA5D-4903-95A0-56C6336E32C9/TAP.docx"; // 同步下载文件
//在下载操作完成之后我们才可以看到"Start DownLoad File......." 消息显示
DownLoadFileSync(downUrl); //异步下载文件
//在下载操作完成之前我们就可以看到"Start DownLoad File......." 消息显示
// DownloadFileAsync(downUrl); Console.WriteLine("Start DownLoad File.........");
Console.ReadLine();
}

调用的是同步方法时,此时会堵塞主线程,直到文件的下载操作被完成之后主线程才继续执行后面的代码,下面是下载文件的同步方法:

private static void DownLoadFileSync(string url)
{
// 创建一个 RequestState 实例
RequestState req = new RequestState();
try
{
// 初始化一个 HttpWebRequest 对象
HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);
// 指派 HttpWebRequest实例到requestState的request字段.
req.request = myHttpWebRequest;
req.response = (HttpWebResponse)myHttpWebRequest.GetResponse();
req.streamResponse = req.response.GetResponseStream();
int readSize = req.streamResponse.

Read

(req.BufferRead, 0, req.BufferRead.Length);
while (readSize > 0)
{
req.filestream.Write(req.BufferRead, 0, readSize);
readSize = req.streamResponse.

Read

(req.BufferRead, 0, req.BufferRead.Length);
} Console.WriteLine("\nThe Length of the File is: {0}", req.filestream.Length);
Console.WriteLine("DownLoad Completely, Download path is: {0}", req.savepath);
}
catch (Exception e)
{
Console.WriteLine("Error Message is:{0}", e.Message);
}
finally
{
req.response.Close();
req.filestream.Close();
}
}

使用同步方法下载文件的运行结果为:

三、异步方法(BeginXXX、EndXXX方法)举例:

控制台程序演示如何使用APM来现异步编程:

private static void DownloadFileAsync(string url)
{
try
{
HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);
RequestState requestState = new RequestState();
requestState.request = myHttpWebRequest;
myHttpWebRequest.BeginGetResponse(new AsyncCallback(ResponseCallback), requestState);
}
catch (Exception e)
{
Console.WriteLine("Error Message is:{0}", e.Message);
}
} //每个异步操作完成时,将调用下面的方法
private static void ResponseCallback(IAsyncResult callbackresult)
{
// 获取 RequestState 对象
RequestState req = (RequestState)callbackresult.AsyncState;
HttpWebRequest myHttpRequest = req.request;
// 结束一个对英特网资源的的异步请求
req.response = (HttpWebResponse)myHttpRequest.EndGetResponse(callbackresult);
//从服务器获取响应流
Stream responseStream = req.response.GetResponseStream();
req.streamResponse = responseStream;
//异步读取流到字节数组
IAsyncResult asynchronousRead = responseStream.

BeginRead

(req.BufferRead, 0, req.BufferRead.Length, ReadCallBack, req);
} // 写字节数组到 FileStream
private static void ReadCallBack(IAsyncResult asyncResult)
{
try
{
// 获取 RequestState 对象
RequestState req = (RequestState)asyncResult.AsyncState;
//从RequestState对象中获取 Response Stream
Stream responserStream = req.streamResponse;
//
int readSize = responserStream.EndRead(asyncResult);
if (readSize > 0)
{
req.filestream.Write(req.BufferRead, 0, readSize);
responserStream.BeginRead(req.BufferRead, 0, req.BufferRead.Length, ReadCallBack, req);//循环调用ReadCallBack方法。
}
else
{
Console.WriteLine("\nThe Length of the File is: {0}", req.filestream.Length);
Console.WriteLine("DownLoad Completely, Download path is: {0}", req.savepath);
req.response.Close();
req.filestream.Close();
}
}
catch (Exception e)
{
Console.WriteLine("Error Message is:{0}", e.Message);
}
}

运行结果为(从运行结果也可以看出,在主线程中调用 DownloadFileAsync(downUrl)方法时,DownloadFileAsync(downUrl)方法中的req.BeginGetResponse调用被没有阻塞调用线程(即主线程),而是立即返回到主线程,是主线程后面的代码可以立即执行)

四、委托实例的异步调用(BeginInvoke、EndInvoke方法

在前面的介绍中已经提到委托类型也会定义了BeginInvoke方法和EndInvoke方法,所以委托类型也实现了异步编程模型,所以可以使用委托的BeginInvokeEndInvoke方法来回调同步方法从而实现异步编程。因为调用委托的BeginInvoke方法来执行一个同步方法时,此时会使用线程池线程回调这个同步方法并立即返回到调用线程中,由于耗时操作在另外一个线程上运行,所以执行BeginInvoke方法的主线程就不会被堵塞。下面实现在线程池线程中如何更新GUI线程中窗体。

// 定义用来实现异步编程的委托
private delegate string AsyncMethodCaller(string fileurl);

       private void btnDownLoad_Click(object sender, EventArgs e)
{
AsyncMethodCaller methodCaller = new AsyncMethodCaller(DownLoadFileSync);//DownLoadFileSync为同步下载文件的方法
methodCaller.

BeginInvoke

(txbUrl.Text.Trim(), 

GetResult

, null);
} // 异步操作完成时执行的方法
private void GetResult(IAsyncResult result)
{
AsyncMethodCaller caller = (AsyncMethodCaller)((AsyncResult)result).AsyncDelegate;// 或者(AsyncMethodCaller)result.AsyncState;
// 调用EndInvoke去等待异步调用完成并且获得返回值
// 如果异步调用尚未完成,则 EndInvoke 会一直阻止调用线程,直到异步调用完成
try{
            string returnstring = caller.EndInvoke(result);
          }
          catch (Exception ex){}
// 然后Invoke方法来使更新GUI操作方法由GUI 线程去执行
Invoke(new MethodInvoker(delegate()
{
rtbState.Text = returnstring;
btnDownLoad.Enabled = true;
}));
}

运行的结果为:

上例子中使用了控件的Invoke方式进行异步回调访问控件的方法,其背后是通过获得GUI线程的同步上文对象,然后同步调用同步上下文对象的post方法把要调用的方法交给GUI线程去处理。

          // 定义用来实现异步编程的委托
private delegate string AsyncMethodCaller(string fileurl); // 定义显示状态的委托
private delegate void ShowStateDelegate(string value);
private ShowStateDelegate showStateCallback; SynchronizationContext sc; public MainForm()
{
InitializeComponent();
txbUrl.Text = "http://download.microsoft.com/download/7/0/3/703455ee-a747-4cc8-bd3e-98a615c3aedb/dotNetFx35setup.exe";
showStateCallback = new ShowStateDelegate(ShowState);
} private void btnDownLoad_Click(object sender, EventArgs e)
{ AsyncMethodCaller methodCaller = new AsyncMethodCaller(DownLoadFileSync);
methodCaller.BeginInvoke(txbUrl.Text.Trim(), GetResult, null); // 捕捉调用线程的同步上下文派生对象

sc =

 SynchronizationContext.Current;
} // 异步操作完成时执行的方法
private void GetResult(IAsyncResult result)
{
AsyncMethodCaller caller = (AsyncMethodCaller)((AsyncResult)result).AsyncDelegate; string returnstring = caller.EndInvoke(result); // 通过获得GUI线程的同步上下文的派生对象,然后调用Post方法来使更新GUI操作方法由GUI 线程去执行
sc.Post(ShowState, returnstring);
} // 显示结果到richTextBox,因为该方法是由GUI线程执行的,所以当然就可以访问窗体控件了
private void ShowState(object result)
{
rtbState.Text = result.ToString();
btnDownLoad.Enabled = true;
}

五、综合实例

假如现在有这样的一个需求,我们需要从3个txt文件中读取字符,然后进行倒序,前提是不能阻塞主线程。如果不用task的话我可能会用工作线程去监视一个bool变量来判断文件是否全部读取完毕,然后再进行倒序,我也说了,相对task来说还是比较麻烦的,这里我就用task来实现。

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);
}

六、小结

  到这里本专题关于异步编程模型的介绍就结束了,异步编程模型(APM)虽然是.NET 1.0中提出来的一个模式,相对于现在来说是旧了点,并且微软现在官方也表明在最新的代码中不推荐使用该模型来实现异步的应用程序,而是推荐使用基于任务的异步编程模型来实现异步的应用程序,但是我个人认为,正是因为它是.NET 1.0中提出的来,并且现在来看确实有些旧了, 所以我们才更应该好好研究下它,因为后面提出的EAP和TAP微软做了更多的封装,是我们对异步编程的本质都不清楚的(其实它们的本质都是使用线程池和委托机制的,具体可以查看前面的相关部分),并且系统学习下异步编程,也可以让我们对新的异步编程模型的所带来的好处有更可直观的认识。在后面的一专题我将带大家全面认识下基于事件的异步编程模型(EAP)。

一、异步编程模型(APM)的更多相关文章

  1. 转:[你必须知道的异步编程]——异步编程模型(APM)

    本专题概要: 引言 你知道APM吗? 你想知道如何使用异步编程模型编写代码吗? 使用委托也可以实现异步编程,你知道否? 小结 一.引言 在前面的C#基础知识系列中介绍了从C#1.0——C#4.0中一些 ...

  2. [你必须知道的异步编程]——异步编程模型(APM)

    本专题概要: 引言 你知道APM吗? 你想知道如何使用异步编程模型编写代码吗? 使用委托也可以实现异步编程,你知道否? 小结 一.引言 在前面的C#基础知识系列中 介绍了从C#1.0——C#4.0中一 ...

  3. 异步编程模型(APM)模式

    什么是APM .net 1.0时期就提出的一种异步模式,并且基于IAsyncResult接口实现BeginXXX和EndXXX类似的方法. .net中有很多类实现了该模式(比如HttpWebReque ...

  4. 异步编程:IAsyncResult异步编程模型 (APM)

    http://www.cnblogs.com/heyuquan/archive/2013/03/22/2976420.html

  5. C#异步编程の-------异步编程模型(APM)

    术语解释: APM               异步编程模型, Asynchronous Programming Model EAP                基于事件的异步编程模式, Event ...

  6. .NET “底层”异步编程模式——异步编程模型(Asynchronous Programming Model,APM)

    本文内容 异步编程类型 异步编程模型(APM) 参考资料 首先澄清,异步编程模式(Asynchronous Programming Patterns)与异步编程模型(Asynchronous Prog ...

  7. 【温故知新】c#异步编程模型(APM)--使用委托进行异步编程

    当我们用到C#类许多耗时的函数XXX时,总会存在同名的类似BeginXXX,EndXXX这样的函数. 例如Stream抽象类的Read函数就有 public abstract int Read(byt ...

  8. c# 基于委托的异步编程模型(APM)测试用例

    很多时候,我们需要程序在执行某个操作完成时,我们能够知道,以便进行下一步操作. 但是在使用原生线程或者线程池进行异步编程,没有一个内建的机制让你知道操作什么时候完成,为了克服这些限制,基于委托的异步编 ...

  9. 《C#并行编程高级教程》第9章 异步编程模型 笔记

    这个章节我个人感觉意义不大,使用现有的APM(异步编程模型)和EAP(基于时间的异步模型)就很够用了,针对WPF和WinForm其实还有一些专门用于UI更新的类. 但是出于完整性,还是将一下怎么使用. ...

随机推荐

  1. java/resteasy批量下载存储在阿里云OSS上的文件,并打包压缩

    现在需要从oss上面批量下载文件并压缩打包,搜了很多相关博客,均是缺胳膊少腿,要么是和官网说法不一,要么就压缩包工具类不给出 官方API https://help.aliyun.com/documen ...

  2. docker创建nginx镜像

    注意:此处不是用的dockerfile创建的镜像,只是用来搞一搞 首先你的系统里面要安装docker,这里就不重复介绍了,可以看之前的文章: 然后再搞一个基础镜像 docker pull regist ...

  3. Nexus-NuGet私有仓库服务搭建(一)

    搭建私有Nuget服务器的方式有很多,大多数人文章介绍在vs 中新建默认web项目,然后再Nuget 中安装 Nuget.Server,再部署到IIS 中即可.虽然能用,但是这种方式太过简陋,操作界面 ...

  4. 一、mysql架构

    一.简介 mysql是一个开源的数据库管理系统,它相对于oracle更加地轻量.成本低,随着功能的日益完善,它变得备受企业喜爱,尤其是中小企业. mysql的整体架构大体包括以下几个方面: 1)主体结 ...

  5. URL 编码 之 我见

    URL编码 编辑 url编码是一种浏览器用来打包表单输入的格式.浏览器从表单中获取所有的name和其中的值 ,将它们以name/value参数编码(移去那些不能传送的字符,将数据排行等等)作为URL的 ...

  6. docker 安装ElasticSearch的中文分词器IK

    首先确保ElasticSearch镜像已经启动 安装插件 方式一:在线安装 进入容器 docker exec -it elasticsearch /bin/bash 在线下载并安装 ./bin/ela ...

  7. Spring学习笔记:面向切面编程AOP(Aspect Oriented Programming)

    一.面向切面编程AOP 目标:让我们可以“专心做事”,避免繁杂重复的功能编码 原理:将复杂的需求分解出不同方面,将公共功能集中解决 *****所谓面向切面编程,是一种通过预编译方式和运行期动态代理实现 ...

  8. K2P刷机教程转自恩山磨人的小妖精

    K2P刷机指南说明 K2P MTK版发布之初用的是22.5.7.85, 这个版本官改和高恪K2P固件都可以从斐讯固件基础上直接升级, 是所谓直刷.但好景不长, 之后的版本比如22.5.17.33就改了 ...

  9. 修改vue的配置项支持生产环境下二级目录访问的方法

    本文主要记录如何配置vue的打包文件配置项,使打包后的文件可以支持二级目录的访问. 1.常规打包 在实际的项目中,我们通常都使用 npm run build 直接打包文件后丢到服务器上访问 打包后的文 ...

  10. Python入门-装饰器初始

    今天我们就围绕一个来展开,那就是:装饰器 一.装饰器 在说装饰器之前,我们先说一个软件设计的原则:开闭原则,又被称为开放封闭原则,你的代码对功能的扩展是开放的,你的程序对修改源代码是封闭的,这样的软件 ...