网站模板如何用,夏天做那些网站致富,免费自助建网站软件,手机网站免费建设平台本专题概要#xff1a;引言你知道APM吗#xff1f;你想知道如何使用异步编程模型编写代码吗#xff1f;使用委托也可以实现异步编程#xff0c;你知道否#xff1f;小结一、引言在前面的C#基础知识系列中介绍了从C#1.0——C#4.0中一些主要特性#xff0c;然而.NET 4.5更新…本专题概要引言你知道APM吗你想知道如何使用异步编程模型编写代码吗使用委托也可以实现异步编程你知道否小结一、引言 在前面的C#基础知识系列中介绍了从C#1.0——C#4.0中一些主要特性然而.NET 4.5更新除了提供了一些新的类和一些新的模板外对于C#语言也做了一定的更新最重要的就是.NET 4.5对应于C#5.0中提供了async和await两个关键字这两个关键字是我们实现异步编程更加容易了其实早在.NET 1.0开始微软就对异步编程做了相应的支持——即异步编程模型(APM), 之后在.NET 2.0中又提出了基于事件的异步编程模型EAP.NET 4.0中又提出了基于任务的异步编程模型TAP。所以为了帮助大家全面理解.NET类库对异步编程的支持这里我把我学习异步编程的一些体会和理解分享出来希望对大家在学习的过程中有所帮助。 在开始讲解APM之前我想先分享一下Visual Studio 版本、C# 版本和.NET 版本的一个对应关系。之所以在这里分享这个对应关系是因为在C#基础知识系列的文章发布之后有些初学者对.NET版本和C#语言特性之间的对应关系有点不清楚有时候会弄混淆了。并且通过这个对应关系也可以帮助大家对C#和.NET 类库有个全面的把控可以帮助大家理清楚C#和.NET 类库中各个知识点使他们可以对号入坐。具体他们的之间对应关系见下表C# 版本.NET Framework版本Visual Studio版本发布日期特性C# 1.0.NET Framework 1.0Visual Studio .NET 20022002.1委托事件APMC# 1.1.NET Framework 1.1Visual Studio .NET 20032003.4C# 2.0.NET Framework 2.0Visual Studio 2005开始命名为Visual Studio2005.11泛型匿名方法迭代器可空类型C# 3.0.NET Framework 3.0.NET Framework 3.5Visual Studio 20082007.11隐式类型的部变量对象集合初始化自动实现属性匿名类型扩展方法查询表达式Lambda表达式表达式树分部类和方法LinqC# 4.0.NET Framework 4.0Visual Studio 20102010.4动态绑定命名和可选参数泛型的协变和逆变互操作性C# 5.0.NET Framework 4.5Visual Studio 20122012.8异步和等待(async和await)调用方信息(Caller Information)二、你知道APM吗 APM即异步编程模型的简写Asynchronous Programming Model大家在写代码的时候或者查看.NET 的类库的时候肯定会经常看到和使用以BeginXXX和EndXXX类似的方法其实你在使用这些方法的时候你就再使用异步编程模型来编写程序。异步编写模型是一种模式该模式允许用更少的线程去做更多的操作.NET Framework很多类也实现了该模式同时我们也可以自定义类来实现该模式也就是在自定义的类中实现返回类型为IAsyncResult接口的BeginXXX方法和EndXXX方法另外委托类型也定义了BeginInvoke和EndInvoke方法并且我们使用WSDL.exe和SvcUtil.exe工具来生成Web服务的代理类型时也会生成使用了APM的BeginXxx和EndXxx方法。下面就具体就拿FileStream类的BeginRead和EndRead方法来介绍下下异步编程模型的实现。BeginXxx方法——开始执行异步操作介绍当需要读取文件中的内容时我们通常会采用FileStream的同步方法Read来读取该同步方法的定义为// 从文件流中读取字节块并将该数据写入给定的字节数组中
// array代表把读取的字节块写入的缓存区
// offset代表array的字节偏量将在此处读取字节
// count 代表最多读取的字节数
public override int Read(byte[] array, int offset, int count ) 该同步方法会堵塞执行的线程当一个WinForm程序需要实现读取一个大文件的内容然后把内容显示在界面时如果我们调用该方法去读取文件的内容时此时Read方法会堵塞UI线程在读取文件内容没有完成之前用户不能对窗体进行任何的操作包括关闭应用程序此时用户看到的该窗体会出现无法响应这样就给用户带来不好一个用户体验从用户角度来看是用户体验不好此时我们自己解决问题的思路肯定是——能不能让读取文件操作在另外一个线程中执行这样就不会堵塞UI线程这时候UI线程继续做属于自己的事情即响应用户的操作。不错微软也肯定也想到了这个解决方案的并且在实际操作中也是这么做的即通过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方法的返回类型就是和同步方法一样的。例如FileStream的EndRead方法返回一个Int32来代表从文件流中实际读取的字节数。对于访问异步操作的结果APM提供了四种方式供开发人员选择在调用BeginXxx方法的线程上调用EndXxx方法来得到异步操作的结果但是这种方式会阻塞调用线程知道操作完成之后调用线程才继续运行查询IAsyncResult的AsyncWaitHandle属性从而得到WaitHandle然后再调用它的WaitOne方法来使一个线程阻塞并等待操作完成再调用EndXxx方法来获得操作的结果。循环查询IAsyncResult的IsComplete属性操作完成后再调用EndXxx方法来获得操作返回的结果。使用 AsyncCallback委托来指定操作完成时要调用的方法在操作完成后调用的方法中调用EndXxx操作来获得异步操作的结果。 在上面的4种方式中第4种方式是APM的首选方式因为此时不会阻塞执行BeginXxx方法的线程然而其他三种都会阻塞调用线程相当于效果和使用同步方法是一样个人感觉根本失去了异步编程的特点所以其他三种方式可以简单了解下在实际异步编程中都是使用委托的方式。 通过上面的介绍大家应该对异步编程模型有了进一步的了解了吧要识别某个类是否实现了异步编程模型只需要看是不是有BeginXxx方法(当然返回类型需要是IAsyncResult)和EndXxx方法。其实异步编程模型这个模式就是微软利用委托和线程池帮助我们实现的一个模式(该模式利用一个线程池线程去执行一个操作在FileStream类BeginRead方法中就是执行一个读取文件操作该线程池线程会立即将控制权返回给调用线程此时线程池线程在后台进行这个异步操作异步操作完成之后通过回调函数来获取异步操作返回的结果。此时就是利用委托的机制。所以说异步编程模式时利用委托和线程池线程搞出来的模式包括后面的基于事件的异步编程和基于任务的异步编程还有C# 5中的async和await关键字都是利用这委托和线程池搞出来的。他们的本质其实都是一样的只是后面提出来的使异步编程更加简单罢了。)既然这里讲到了FileStream对象这里就提出一个关于该类值得注意的地方的FileStream对象默认情况下是同步打开操作系统句柄当我们创建一个FileStream对象没有为其指定FileOptions.Asynchronous参数或者没有显示指定useAsync为true时Windows 操作系统会以同步的方法执行所有的文件操作即使此时你还是可以调用BeginRead方法。但是这样对于你的应用程序操作只是表面上是异步执行的但FileStream类在内部会用另一个线程模拟异步行为。同样道理当创建的FileStream对象指定了FileOptions.Asynchronous参数时然后我们仍然可以调用Read同步方法此时在内部FileStream类会开始一个异步操作并立即使调用线程进入睡眠状态知道操作完成才会唤醒通过这样来模拟同步行为。因此在使用FileStream对象时需要先决定是同步执行还是异步执行。并显示地指定FileOptions.Asynchronous参数或useAsync参数。三、你想知道如何使用异步编程模型编写代码吗 介绍了这么久的异步编程模型大家肯定很迫不及待地想使用异步编程模型来改写自己的同步应用程序或者实现一个异步的应用程序。下面就通过一个例子来演示如何使用APM来现异步编程该程序也实现了一个同步方法为了让大家更好地体会同步线程和异步线程的区别本程序的实现是一个控制台程序大家也可以很好地一直与WinForm应用程序和WPF程序#region use APM to download file asynchronouslyprivate static void DownloadFileAsync(string url){try{// Initialize an HttpWebRequest objectHttpWebRequest myHttpWebRequest (HttpWebRequest)WebRequest.Create(url);// Create an instance of the RequestState and assign HttpWebRequest instance to its request field.RequestState requestState new RequestState();requestState.request myHttpWebRequest;myHttpWebRequest.BeginGetResponse(new AsyncCallback(ResponseCallback), requestState);}catch (Exception e){Console.WriteLine(Error Message is:{0},e.Message);}}// The following method is called when each asynchronous operation completes.private static void ResponseCallback(IAsyncResult callbackresult){// Get RequestState objectRequestState myRequestState (RequestState)callbackresult.AsyncState;HttpWebRequest myHttpRequest myRequestState.request;// End an Asynchronous request to the Internet resourcemyRequestState.response (HttpWebResponse)myHttpRequest.EndGetResponse(callbackresult);// Get Response Stream from ServerStream responseStream myRequestState.response.GetResponseStream();myRequestState.streamResponse responseStream;IAsyncResult asynchronousRead responseStream.BeginRead(myRequestState.BufferRead, 0, myRequestState.BufferRead.Length, ReadCallBack, myRequestState); }// Write bytes to FileStreamprivate static void ReadCallBack(IAsyncResult asyncResult){try{// Get RequestState objectRequestState myRequestState (RequestState)asyncResult.AsyncState;// Get Response Stream from ServerStream responserStream myRequestState.streamResponse;//int readSize responserStream.EndRead(asyncResult);if (readSize 0){myRequestState.filestream.Write(myRequestState.BufferRead, 0, readSize);responserStream.BeginRead(myRequestState.BufferRead, 0, myRequestState.BufferRead.Length, ReadCallBack, myRequestState);}else{Console.WriteLine(\nThe Length of the File is: {0}, myRequestState.filestream.Length);Console.WriteLine(DownLoad Completely, Download path is: {0}, myRequestState.savepath);myRequestState.response.Close();myRequestState.filestream.Close();} }catch (Exception e){Console.WriteLine(Error Message is:{0}, e.Message);}}#endregion运行结果为从运行结果也可以看出在主线程中调用 DownloadFileAsync(downUrl)方法时DownloadFileAsync(downUrl)方法中的myHttpWebRequest.BeginGetResponse调用被没有阻塞调用线程(即主线程)而是立即返回到主线程是主线程后面的代码可以立即执行如果我们调用的是同步方法时此时会堵塞主线程直到文件的下载操作被完成之后主线程才继续执行后面的代码下面是下载文件的同步方法#region Download File Synchrouslyprivate static void DownLoadFileSync(string url){// Create an instance of the RequestStateRequestState requestStatenew RequestState();try{// Initialize an HttpWebRequest objectHttpWebRequest myHttpWebRequest (HttpWebRequest)WebRequest.Create(url);// assign HttpWebRequest instance to its request field.requestState.request myHttpWebRequest;requestState.response (HttpWebResponse)myHttpWebRequest.GetResponse();requestState.streamResponse requestState.response.GetResponseStream();int readSize requestState.streamResponse.Read(requestState.BufferRead, 0, requestState.BufferRead.Length);while (readSize 0){requestState.filestream.Write(requestState.BufferRead, 0, readSize);readSize requestState.streamResponse.Read(requestState.BufferRead, 0, requestState.BufferRead.Length);}Console.WriteLine(\nThe Length of the File is: {0}, requestState.filestream.Length);Console.WriteLine(DownLoad Completely, Download path is: {0}, requestState.savepath);}catch (Exception e){Console.WriteLine(Error Message is:{0}, e.Message);}finally{requestState.response.Close();requestState.filestream.Close();}}#endregion使用同步方法下载文件的运行结果为大家可以对照两个方式的结果就可以明显看出他们的区别了。四、使用委托也可以实现异步编程你知道否 在前面的介绍中已经提到委托类型也会定义了BeginInvoke方法和EndInvoke方法所以委托类型也实现了异步编程模型所以可以使用委托的BeginInvoke和EndInvoke方法来回调同步方法从而实现异步编程。因为调用委托的BeginInvoke方法来执行一个同步方法时此时会使用线程池线程回调这个同步方法并立即返回到调用线程中由于耗时操作在另外一个线程上运行所以执行BeginInvoke方法的主线程就不会被堵塞。但是这里存在的一个问题时因为同步方法在另外一个线程中执行的然而我们怎么把同步方法执行的状态反应到UI界面上来呢因为在GUI应用程序包括Windows窗体WPF和Silverlight中,创建窗口的线程是唯一能够对那个窗口进行更新的线程所以在执行同步方法的线程就不能对窗口中的控件进行操作也就不能把方法允许的结果反应到窗体上了。这里有两种解决方案一种是设置控件的CheckForIllegalCrossThreadCalls 属性为false设置为false的意思代表允许跨线程调用这种方式虽然可以解决该问题但是不推荐因为它违背了.NET安全规范第二种就是使用SynchronizationContext基类该类记录着线程的同步上下文对象我们可以通过在GUI线程中调用SynchronizationContext.Current属性来获得GUI线程的同步上下文然后当线程池线程需要更新窗体时可以调用保存的SynchronizationContext派生对象的Post方法(Post方法会将回调函数送到GUI线程的队列中每个线程都有各自的操作队列的线程的执行都是从这个队列中拿方法去执行)向Post方法传递要由GUI线程调用的方法(该方法的定义要匹配SendOrPostCallback委托的签名)还需要想Post方法传递一个要传给回调方法的参数。4.1 使用委托实现更好的用户体验——不堵塞UI线程虽然第一种方案是一种不推荐的方案但是我觉得有些朋友还是不知道怎么实现的所以在这部分就用具体的代码来实现下并且该实现也可以与使用同步上下文对象的方式进行对比这样大家就可以更加了解如何使用委托来进行异步编程了。下面就具体看实现代码吧View Code// 定义用来实现异步编程的委托private delegate string AsyncMethodCaller(string fileurl);public Mainform(){InitializeComponent();txbUrl.Text http://download.microsoft.com/download/7/0/3/703455ee-a747-4cc8-bd3e-98a615c3aedb/dotNetFx35setup.exe;// 允许跨线程调用// 实际开发中不建议这样做的违背了.NET 安全规范CheckForIllegalCrossThreadCalls false;}private void btnDownLoad_Click(object sender, EventArgs e){rtbState.Text Download............;if (txbUrl.Text string.Empty){MessageBox.Show(Please input valid download file url);return;}AsyncMethodCaller methodCaller new AsyncMethodCaller(DownLoadFileSync);methodCaller.BeginInvoke(txbUrl.Text.Trim(), GetResult, null);}// 同步下载文件的方法// 该方法会阻塞主线程使用户无法对界面进行操作// 在文件下载完成之前用户甚至都不能关闭运行的程序。private string DownLoadFileSync(string url){// Create an instance of the RequestStateRequestState requestState new RequestState();try{// Initialize an HttpWebRequest objectHttpWebRequest myHttpWebRequest (HttpWebRequest)WebRequest.Create(url);// assign HttpWebRequest instance to its request field.requestState.request myHttpWebRequest;requestState.response (HttpWebResponse)myHttpWebRequest.GetResponse();requestState.streamResponse requestState.response.GetResponseStream();int readSize requestState.streamResponse.Read(requestState.BufferRead, 0, requestState.BufferRead.Length);while (readSize 0){requestState.filestream.Write(requestState.BufferRead, 0, readSize);readSize requestState.streamResponse.Read(requestState.BufferRead, 0, requestState.BufferRead.Length);}// 执行该方法的线程是线程池线程该线程不是与创建richTextBox控件的线程不是一个线程// 如果不把 CheckForIllegalCrossThreadCalls 设置为false该程序会出现“不能跨线程访问控件”的异常return string.Format(The Length of the File is: {0}, requestState.filestream.Length) string.Format(\nDownLoad Completely, Download path is: {0}, requestState.savepath);}catch (Exception e){return string.Format(Exception occurs in DownLoadFileSync method, Error Message is:{0}, e.Message);}finally{requestState.response.Close();requestState.filestream.Close();}}// 异步操作完成时执行的方法private void GetResult(IAsyncResult result){AsyncMethodCaller caller (AsyncMethodCaller)((AsyncResult)result).AsyncDelegate;// 调用EndInvoke去等待异步调用完成并且获得返回值// 如果异步调用尚未完成则 EndInvoke 会一直阻止调用线程直到异步调用完成string returnstring caller.EndInvoke(result);//sc.Post(ShowState,resultvalue);rtbState.Text returnstring; }运行的结果为4.2 在线程中访问另一个线程创建的控件这部分将使用同步上下文的方式来实现在线程池线程中如何更新GUI线程中窗体因为在程序代码部分都有详细的解释这里就直接贴代码了public partial class MainForm : Form{// 定义用来实现异步编程的委托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){rtbState.Text Download............;btnDownLoad.Enabled false;if (txbUrl.Text string.Empty){MessageBox.Show(Please input valid download file url);return;}AsyncMethodCaller methodCaller new AsyncMethodCaller(DownLoadFileSync);methodCaller.BeginInvoke(txbUrl.Text.Trim(), GetResult, null);// 捕捉调用线程的同步上下文派生对象sc SynchronizationContext.Current;}// 同步下载文件的方法// 该方法会阻塞主线程使用户无法对界面进行操作// 在文件下载完成之前用户甚至都不能关闭运行的程序。private string DownLoadFileSync(string url){// Create an instance of the RequestStateRequestState requestState new RequestState();try{// Initialize an HttpWebRequest objectHttpWebRequest myHttpWebRequest (HttpWebRequest)WebRequest.Create(url);// assign HttpWebRequest instance to its request field.requestState.request myHttpWebRequest;requestState.response (HttpWebResponse)myHttpWebRequest.GetResponse();requestState.streamResponse requestState.response.GetResponseStream();int readSize requestState.streamResponse.Read(requestState.BufferRead, 0, requestState.BufferRead.Length);while (readSize 0){requestState.filestream.Write(requestState.BufferRead, 0, readSize);readSize requestState.streamResponse.Read(requestState.BufferRead, 0, requestState.BufferRead.Length);}// 执行该方法的线程是线程池线程该线程不是与创建richTextBox控件的线程不是一个线程// 如果不把 CheckForIllegalCrossThreadCalls 设置为false该程序会出现“不能跨线程访问控件”的异常return string.Format(The Length of the File is: {0}, requestState.filestream.Length) string.Format(\nDownLoad Completely, Download path is: {0}, requestState.savepath);}catch (Exception e){return string.Format(Exception occurs in DownLoadFileSync method, Error Message is:{0}, e.Message);}finally{requestState.response.Close();requestState.filestream.Close();}}// 异步操作完成时执行的方法private void GetResult(IAsyncResult result){AsyncMethodCaller caller (AsyncMethodCaller)((AsyncResult)result).AsyncDelegate;// 调用EndInvoke去等待异步调用完成并且获得返回值// 如果异步调用尚未完成则 EndInvoke 会一直阻止调用线程直到异步调用完成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;}}程序的运行结果和前面使用第一方案的结果是一样的,这里就不重复贴图了上面所有的实现都是部分代码你可以在文章的最后下载本专题的所有源码。五、小结 到这里本专题关于异步编程模型的介绍就结束了异步编程模型APM虽然是.NET 1.0中提出来的一个模式相对于现在来说是旧了点并且微软现在官方也表明在最新的代码中不推荐使用该模型来实现异步的应用程序而是推荐使用基于任务的异步编程模型来实现异步的应用程序但是我个人认为正是因为它是.NET 1.0中提出的来并且现在来看确实有些旧了 所以我们才更应该好好研究下它因为后面提出的EAP和TAP微软做了更多的封装是我们对异步编程的本质都不清楚的其实它们的本质都是使用线程池和委托机制的具体可以查看前面的相关部分并且系统学习下异步编程也可以让我们对新的异步编程模型的所带来的好处有更可直观的认识。在后面的一专题我将带大家全面认识下基于事件的异步编程模型EAP。