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

术语解释:

APM               异步编程模型, Asynchronous Programming Model

EAP                基于事件的异步编程模式, Event-based Asynchronous Pattern

TAP                基于任务的异步编程模式, Task-based Asynchronous Pattern

 

一、异步编程

APM即异步编程模型(Asynchronous Programming Model),大家在写代码的时候,或者查看.NET 的类库的时候,肯定会经常看到和使用以BeginXXX和EndXXX类似的方法,其实你在使用这些方法的时候,你就在使用异步编程模型来编写程序。异步编写模型是一种模式,该模式允许用更少的线程去做更多的操作,.NET Framework很多类也实现了该模式,同时我们也可以自定义类来实现该模式,(也就是在自定义的类中实现返回类型为IAsyncResult接口的BeginXXX方法和EndXXX方法)另外委托类型也定义了BeginInvoke和EndInvoke方法,并且我们使用WSDL.exe和SvcUtil.exe工具来生成Web服务的代理类型时,也会生成使用了APM的BeginXxx和EndXxx方法。下面就具体就拿FileStream类的BeginReadEndRead方法来介绍下下异步编程模型的实现。

读取的同步方法:

public override int Read(byte[] array, int offset, int count )

读取过程中,执行此方法会阻塞主线程,最明显的就是造成界面卡死。

读取的异步方法:

// 开始异步读操作

// 前面的3个参数和同步方法代表的意思一样,这里就不说了,可以看到这里多出了2个参数

// userCallback代表当异步IO操作完成时,你希望由一个线程池线程执行的方法,该方法必须匹配AsyncCallback委托

// stateObject代表你希望转发给回调方法的一个对象的引用,在回调方法中,可以查询IAsyncResult接口的AsyncState属性来访问该对象

public override IAsyncResult BeginRead(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject)

 

读取完成后如果不需要进行其他操作,可以设置为null

 

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

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

异步调用的返回值:对于访问异步操作的返回值,APM提供了四种方式供开发人员选择:

  1. 在调用BeginXxx方法的线程上调用EndXxx方法来得到异步操作的返回值,但是这种方式会阻塞调用线程,只到操作完成之后调用线程才继续运行。

  2. 查询IAsyncResultAsyncWaitHandle属性,从而得到WaitHandle,然后再调用它的WaitOne方法来使一个线程阻塞并等待被调用线程操作完成,再调用EndXxx方法来获得操作的返回值。

  3. 循环查询IAsyncResultIsComplete属性,操作完成后再调用EndXxx方法来获得操作返回的结果。

  4. 使用 AsyncCallback委托来指定操作完成时要调用的方法,在操作完成后调用的方法中调用EndXxx操作来获得异步操作的结果。(最为常用)

异步编程模型的本质:

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

复制代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Windows.Forms;//https://msdn.microsoft.com/zh-cn/library/system.net.httpwebrequest.begingetresponse.aspxnamespace WindowsFormsApplication1
{public class RequestState{// This class stores the State of the request.const int BUFFER_SIZE = 1024;public StringBuilder requestData;public byte[] BufferRead;public HttpWebRequest request;public HttpWebResponse response;public Stream streamResponse;public Stream filestream;public string savepath;public RequestState(){BufferRead = new byte[BUFFER_SIZE];requestData = new StringBuilder("");request = null;streamResponse = null;}}public partial class Form1 : Form{// 定义用来实现异步编程的委托private delegate string AsyncMethodCaller(string fileurl);public Form1(){InitializeComponent();tbUrl.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){rtbContent.Text = "Download............";if (tbUrl.Text == string.Empty){MessageBox.Show("Please input valid download file url");return;}AsyncMethodCaller methodCaller = new AsyncMethodCaller(DownLoadFileSync);methodCaller.BeginInvoke(tbUrl.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);rtbContent.Text = returnstring;}}
}

复制代码

复制代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Windows.Forms;//https://msdn.microsoft.com/zh-cn/library/system.net.httpwebrequest.begingetresponse.aspxnamespace WindowsFormsApplication1
{public class RequestState{// This class stores the State of the request.const int BUFFER_SIZE = 1024;public StringBuilder requestData;public byte[] BufferRead;public HttpWebRequest request;public HttpWebResponse response;public Stream streamResponse;public Stream filestream;public string savepath;public RequestState(){BufferRead = new byte[BUFFER_SIZE];requestData = new StringBuilder("");request = null;streamResponse = null;}}public partial class Form1 : Form{// 定义用来实现异步编程的委托private delegate string AsyncMethodCaller(string fileurl);public Form1(){InitializeComponent();tbUrl.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){rtbContent.Text = "Download............";if (tbUrl.Text == string.Empty){MessageBox.Show("Please input valid download file url");return;}AsyncMethodCaller methodCaller = new AsyncMethodCaller(DownLoadFileSync);methodCaller.BeginInvoke(tbUrl.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);rtbContent.Text = returnstring;}}
}

复制代码

当我们调用实现基于事件的异步模式的类的 XxxAsync方法时,即代表开始了一个异步操作,该方法调用完之后会使一个线程池线程去执行耗时的操作,所以当UI线程调用该方法时,当然也就不会堵塞UI线程了。并且基于事件的异步模式是建立了APM的基础之上的(这也是我在上一专题中详解介绍APM的原因),而APM又是建立了在委托之上的

二、基于事件的异步编程

基于事件的C#异步编程模式是比IAsyncResult模式更高级的一种异步编程模式,也被用在更多的场合。该异步模式具有以下优点:

·                  “在后台”执行耗时任务(例如下载和数据库操作),但不会中断您的应用程序。

·                  同时执行多个操作,每个操作完成时都会接到通知(在通知中可以区分是完成了哪个操作)。

·                  等待资源变得可用,但不会停止(“挂起”)您的应用程序。

·                  使用熟悉的事件和委托模型与挂起的异步操作通信。

对于相对简单的应用程序可以直接用 .Net 2.0 新增的 BackgroundWorker 组件来很方便的实现,对于更复杂的异步应用程序则需要自己实现一个符合基于事件的C#异步编程模式的类。在实现基于事件的异步模式的设计前,需要了解基于事件的异步模式的实现原理是什么。基于事件的异步模式需要以下三个类型的帮助。

AsyncOperation:提供了对异步操作的生存期进行跟踪的功能,包括操作进度通知和操作完成通知,并确保在正确的线程或上下文中调用客户端的事件处理程序。

public void Post(SendOrPostCallback d,Object arg);

public void PostOperationCompleted(SendOrPostCallback d,Object arg);

通过在异步辅助代码中调用Post方法把进度和中间结果报告给用户,如果是取消异步任务或提示异步任务已完成,则通过调用PostOperationCompleted方法结束异步操作的跟踪生命期。在PostOperationCompleted方法调用后,AsyncOperation对象变得不再可用,再次访问将引发异常。在此有个问题:在该异步模式中,通过AsyncOperation的Post函数来通知进度的时候,是如何使SendOrPostCallback委托在UI线程上执行的?针对该问题下文有具体分析。

AsyncOperationManager:为AsyncOperation对象的创建提供了便捷方式,通过CreateOperation方法可以创建多个AsyncOperation实例,实现对多个异步操作进行跟踪。 

WindowsFormsSynchronizationContext:该类继承自SynchronizationContext类型,提供 Windows 窗体应用程序模型的同步上下文。该类型是基于事件异步模式通信的核心。之所以说该类型是基于事件异步模式的通信核心,是因为该类型解决了“保证SendOrPostCallback委托在UI线程上执行”的问题。它是如何解决的?请看AsyncOperation类型的Post方法的实现:

 

/// <summary>  /// AsyncOperation类型的Post方法的实现  /// </summary>  
public void Post(SendOrPostCallback d, object arg)  
{  this.VerifyNotCompleted();  this.VerifyDelegateNotNull(d);  this.syncContext.Post(d, arg);  
}  

 

在AsyncOperation类型的Post方法中,直接调用了SynchronizationContext类型的Post方法,再看该Post方法的实现:

 

/// <summary>  /// WindowsFormsSynchronizationContext类型的Post方法的实现  /// </summary>  
public override void Post(SendOrPostCallback d, object state)  
{  if (this.controlToSendTo != null)  {  this.controlToSendTo.BeginInvoke(d, new object[] { state }); //此处保证了SendOrPostCallBack委托在UI线程上执行  }  
}  

 


例子:

 

using System;  
using System.Collections.Generic;  
using System.Text;  
using System.ComponentModel;  
using System.Collections.Specialized;  
using System.Threading;  namespace test  
{  /// <summary>  /// 任务1的进度通知代理  /// </summary>  /// <param name="sender"></param>  /// <param name="e"></param>  public delegate void Work1ProgressChangedEventHandler(object sender, Work1ProgressChangedEventArgs e);  /// <summary>  /// 任务1的进度通知参数  /// </summary>  /// <param name="sender"></param>  /// <param name="e"></param>  public delegate void Work1CompletedEventHandler(object sender, Work1CompletedEventArgs e);  public class BasedEventAsyncWorker  {  private delegate void WorkerEventHandler(int maxNumber, AsyncOperation asyncOp);  private HybridDictionary userStateToLifetime = new HybridDictionary();  public BasedEventAsyncWorker()  { }  #region DoWork1的基于事件的异步调用  public void DoWork1Async(object userState, int maxNumber)  {  AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userState);  //userStateToLifetime有可能会同时被多线程访问,在此需要lock进行同步处理  lock (userStateToLifetime.SyncRoot)  {  if (userStateToLifetime.Contains(userState))  {  throw new ArgumentException(  "userState parameter must be unique",  "userState");  }  userStateToLifetime[userState] = asyncOp;  }  //异步开始任务1  WorkerEventHandler workerDelegate = new WorkerEventHandler(DoWork1);  workerDelegate.BeginInvoke(maxNumber, asyncOp, null, null);  }  private void DoWork1(int maxNumber, AsyncOperation asyncOp)  {  Exception e = null;  //判断该userState的任务仍在处理中  if (!TaskCanceled(asyncOp.UserSuppliedState))  {  try  {  int n = 0;  int percentage = 0;  while (n < maxNumber && !TaskCanceled(asyncOp.UserSuppliedState))  {  Thread.Sleep(100); //模拟耗时操作  percentage = (int)((float)n / (float)maxNumber * 100);  Work1ProgressChangedEventArgs progressChanageArgs =  new Work1ProgressChangedEventArgs(maxNumber, percentage, asyncOp.UserSuppliedState);  //任务1的进度通知  asyncOp.Post(new SendOrPostCallback(Work1ReportProgressCB), progressChanageArgs);   n++;  }  }  catch (Exception ex)  {  e = ex;  }  }  this.Work1Complete(e, TaskCanceled(asyncOp.UserSuppliedState), asyncOp);  }  private void Work1Complete(Exception exception, bool canceled, AsyncOperation asyncOp)  {  if (!canceled)  {  lock (userStateToLifetime.SyncRoot)  {  userStateToLifetime.Remove(asyncOp.UserSuppliedState);  }  }  Work1CompletedEventArgs e = new Work1CompletedEventArgs(exception, canceled, asyncOp.UserSuppliedState);  //通知指定的任务已经完成  asyncOp.PostOperationCompleted(new SendOrPostCallback(Work1CompleteCB), e);  //调用 PostOperationCompleted 方法来结束异步操作的生存期。  //为某个特定任务调用此方法后,再调用其相应的 AsyncOperation 对象会引发异常。  }  private void Work1ReportProgressCB(object state)  {  Work1ProgressChangedEventArgs e = state as Work1ProgressChangedEventArgs;  OnWork1ProgressChanged(e);  }  private void Work1CompleteCB(object state)  {  Work1CompletedEventArgs e = state as Work1CompletedEventArgs;  OnWork1Completed(e);  }  #region Work1的进度通知和任务完成的事件  public event Work1ProgressChangedEventHandler Work1ProgressChanged;  protected virtual void OnWork1ProgressChanged(Work1ProgressChangedEventArgs e)  {  Work1ProgressChangedEventHandler temp = this.Work1ProgressChanged;  if (temp != null)  {  temp(this, e);  }  }  public event Work1CompletedEventHandler Work1Completed;  protected virtual void OnWork1Completed(Work1CompletedEventArgs e)  {  Work1CompletedEventHandler temp = this.Work1Completed;  if (temp != null)  {  temp(this, e);  }  }   #endregion   #endregion  /// <summary>  /// 取消指定userState的任务执行  /// </summary>  /// <param name="userState"></param>  public void CancelAsync(object userState)  {  AsyncOperation asyncOp = userStateToLifetime[userState] as AsyncOperation;  if (asyncOp != null)  {  lock (userStateToLifetime.SyncRoot)  {  userStateToLifetime.Remove(userState);  }  }  }  /// <summary>  /// 判断指定userState的任务是否已经被结束。返回值:true 已经结束; false 还没有结束  /// </summary>  /// <param name="userState"></param>  /// <returns></returns>  private bool TaskCanceled(object userState)  {  return (userStateToLifetime[userState] == null);  }  }  public class Work1ProgressChangedEventArgs :ProgressChangedEventArgs  {  private int totalWork = 1;  public Work1ProgressChangedEventArgs(int totalWork, int progressPercentage, object userState)  : base(progressPercentage, userState)  {  this.totalWork = totalWork;  }  /// <summary>  /// Work1的总工作量  /// </summary>  public int TotalWork  {  get  {  return totalWork;  }  }  }  public class Work1CompletedEventArgs : AsyncCompletedEventArgs  {  public Work1CompletedEventArgs(Exception e, bool canceled, object state)  : base(e, canceled, state)  {  }  }  }  

复制代码

 

三、基于任务的异步编程

基于任务的异步模式(Task-based Asynchronous Pattern,TAP)之所以被微软所推荐,主要就它使用简单,基于任务的异步模式使用单个方法来表示异步操作的开始和完成,然而异步编程模型(APM)却要求BeginXxx和EndXxx两个方法来分别表示异步操作的开始和完成(这样使用起来就复杂了),然而,基于事件的异步模式(EAP)要求具有Async后缀的方法和一个或多个事件、事件处理程序和事件参数。看到这里,是不是大家都有这样一个疑问的——我们怎样区分.NET类库中的类实现了基于任务的异步模式呢? 这个识别方法很简单,当看到类中存在TaskAsync为后缀的方法时就代表该类实现了TAP并且基于任务的异步模式同样也支持异步操作的取消和进度的报告的功能,但是这两个实现都不像EAP中实现的那么复杂,因为如果我们要自己实现EAP的类,我们需要定义多个事件和事件处理程序的委托类型和事件的参数(具体可以查看上一专题中的BackgroundWorker剖析部分),但是在TAP实现中,我们只需要通过向异步方法传入CancellationToken参数,因为在异步方法内部会对这个参数的IsCancellationRequested属性进行监控,当异步方法收到一个取消请求时,异步方法将会退出执行(具体这点可以使用反射工具查看WebClient的DownloadDataTaskAsync方法,同时也可以参考我后面部分自己实现基于任务的异步模式的异步方法。),在TAP中,我们可以通过IProgress<T>接口来实现进度报告的功能,具体实现可以参考我后面的程序部分。

目前我还没有找到在.NET 类库中实现了基于任务的异步模式的哪个类提供进度报告的功能,下面的将为大家演示这个实现,并且也是这个程序的亮点,同时通过自己实现TAP的异步方法来进一步理解基于任务的异步模式。

如何基于任务异步编程

复制代码

//  Download File// CancellationToken 参数赋值获得一个取消请求// progress参数负责进度报告private void DownLoadFile(string url, CancellationToken ct, IProgress<int> progress){HttpWebRequest request = null;HttpWebResponse response = null;Stream responseStream = null;int bufferSize = 2048;byte[] bufferBytes = new byte[bufferSize];try{request = (HttpWebRequest)WebRequest.Create(url);if (DownloadSize != 0){request.AddRange(DownloadSize);}response = (HttpWebResponse)request.GetResponse();responseStream = response.GetResponseStream();int readSize = 0;while (true){// 收到取消请求则退出异步操作if (ct.IsCancellationRequested == true){MessageBox.Show(String.Format("下载暂停,下载的文件地址为:{0}\n 已经下载的字节数为: {1}字节", downloadPath, DownloadSize));response.Close();filestream.Close();sc.Post((state) =>{this.btnStart.Enabled = true;this.btnPause.Enabled = false;}, null);// 退出异步操作break;}readSize = responseStream.Read(bufferBytes, 0, bufferBytes.Length);if (readSize > 0){DownloadSize += readSize;int percentComplete = (int)((float)DownloadSize / (float)totalSize * 100);filestream.Write(bufferBytes, 0, readSize);// 报告进度progress.Report(percentComplete);}else{MessageBox.Show(String.Format("下载已完成,下载的文件地址为:{0},文件的总字节数为: {1}字节", downloadPath, totalSize));sc.Post((state) =>{this.btnStart.Enabled = false;this.btnPause.Enabled = false;}, null);response.Close();filestream.Close();break;}}  }catch (AggregateException ex){// 因为调用Cancel方法会抛出OperationCanceledException异常// 将任何OperationCanceledException对象都视为以处理ex.Handle(e => e is OperationCanceledException);}}

复制代码


四、Async和await关键字(C#5.0,.net4.5以后)

”async”和”await”:你用我用,才有用;你用我不用,等于没用。

五、异步编程几种方式的比较,引同行文章http://www.cnblogs.com/durow/p/4826653.html

0x00 引言

之前写程序的时候在遇到一些比较花时间的操作例如HTTP请求时,总是会new一个Thread处理。对XxxxxAsync()之类的方法也没去了解过,倒也没遇到什么大问题。最近因为需求要求用DevExpress写界面,跑起来后发现比Native控件效率差好多。这才想到之前看到的“金科玉律”:不要在UI线程上执行界面无关的操作,因此集中看了下C#的异步操作,分享一下自己的比较和总结。

0x01 测试方法

IDE:VS2015 Community

.NET版本:4.5

使用函数随机休眠100到500毫秒来模拟耗时任务,并返回任务花费的时间,在UI线程上调用这个方法会造成阻塞,导致UI假死,因此需要通过异步方式执行这个任务,并在信息输出区域显示花费的时间。

 

主界面中通过各种不同按钮测试不同类型的异步操作

 

0x02 使用Thread进行异步操作

使用ThreadPool进行异步操作的方法如下所示,需要注意的就是IsBackground默认为false,也就是该线程对调用它的线程不产生依赖,当调用线程退出时该线程也不会结束。因此需要将IsBackground设置为true以指明该线程是后台线程,这样当主线程退出时该线程也会结束。另外跨线程操作UI还是要借助Dispatcher.BeginInvoke(),如果需要阻塞UI线程可以使用Dispatcher.Invoke()。

 

0x03 使用ThreadPool进行异步操作

ThreadPool(线程池)的出现主要就是为了提高线程的复用(类似的还有访问数据库的连接池)。线程的创建是开销比较大的行为,为了达到较好的交互体验,开发中可能会大量使用异步操作,特别是需要频繁进行大量的短时间的异步操作时,频繁创建和销毁线程会在造成很多资源的浪费。而通过在线程池中存放一些线程,当需要新建线程执行操作时就从线程池中取出一个已经存在的空闲线程使用,如果此时没有空闲线程,且线程池中的线程数未达到线程池上限,则新建一个线程,使用完成后再放回到线程池中。这样可以极大程度上省去线程创建的开销。线程池中线程的最小和最大数都可以指定,不过多数情况下无需指定,CLR有一套管理线程池的策略。ThreadPool的使用非常简单,代码如下所示。跨线程操作UI仍需借助Dispatcher。

 

0x04 使用Task进行异步操作

Task进行异步操作时也是从线程池中获取线程进行操作,不过支持的操作更加丰富一些。而且Task<T>可以支持返回值,通过Task的ContinueWith()可以在Task执行结束后将返回值传入以进行操作,但在ContinueWith中跨线程操作UI仍需借助Dispatcher。另外Task也可以直接使用静态方法Task.Run<T>()执行异步操作。

 

0x05 使用async/await进行异步操作

这个是C#5中的新特性,当遇到await时,会从线程池中取出一个线程异步执行await等待的操作,然后方法立即返回。等异步操作结束后回到await所在的地方接着往后执行。await需要等待async Task<T>类型的函数。详细的使用方法可参考相关资料,测试代码如下所示。异步结束后的会返回到调用线程,所以修改UI不需要Dispatcher。

 

也可以把TestTask包装成async方法,这样就可以使用上图中注释掉的两行代码进行处理。包装后的异步方法如下所示:

 

async/await也是从线程池中取线程,可实现线程复用,而且代码简洁容易阅读,异步操作返回后会自动返回调用线程,是执行异步操作的首选方式。而且虽然是C#5的新特性,但C#4可以通过下载升级包来支持async/await。

0x06 关于效率

以上尝试的方法除了直接使用Thread之外,其他几种都是直接或间接使用线程池来获取线程的。从理论上来分析,创建线程时要给线程分配栈空间,线程销毁时需要回收内存,创建线程也会增加CPU的工作。因此可以连续创建线程并记录消耗的时间来测试性能。测试代码如下所示:

 

当测试Thread时每次测试在连续创建线程时内存和CPU都会有个小突起,不过在线程结束后很快就会降下去,在我的电脑上连续创建100个线程大概花费120-130毫秒。如图所示:

 

测试结果:

 

使用基于线程池的方法创建线程时,有时第一次会稍慢一些,应该是线程池内线程不足,时间开销在0-15毫秒,第一次创建内存也会上升。后面再测试时时间开销为0毫秒,内存表现也很平稳,CPU开销分布比较平均。测试结果如图所示:

 

0x07 结论

在执行异步操作时应使用基于线程池的操作,从代码的简洁程度和可读性上优先使用async/await方式。对于较老的.NET版本可以使用Task或ThreadPool。符合以下情况的可以使用Thread:

1、线程创建后需要持续工作到主线程退出的。这种情况下就算使用线程池线程也不会归还,实现不了复用,可以使用Thread。

2、线程在主线程退出后仍需要执行的,这种情况使用线程池线程无法满足需求,需要使用Thread并制定IsBackground为false(默认)。

0x08 相关下载

测试程序代码在:https://github.com/durow/TestArea/tree/master/AsyncTest/AsyncTest

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/438342.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【LeetCode-SQL每日一练】—— 627. 变更性别

&#x1f388;写在前面 &#x1f64b;‍♂️大家好呀&#xff0c;我是超梦。大家可以叫我小梦~ 又到了练习SQL的时候啦&#xff01;一起来学习吧&#xff01; &#x1f64b;‍♂️ 小伙伴们如果在学习过程中有不明白的地方&#xff0c;欢迎评论区留言提问&#xff0c;小梦定知无…

iis运行原理 Asp.Net详解IIS内部运行原理

本章节主要讲IIS和 管道内部如何处理客户端Http请求&#xff0c;会较多的以代码的形式讲述&#xff0c;让大家对HttpApplication、HttpHandler、HttpApplicationFactory、Page这几个在处理请求过程中扮演重要角色的对象有更深入的了解。 下面我们通过单步调式跟踪System.Web.D…

【LeetCode-SQL每日一练】—— 1179. 重新格式化部门表

&#x1f388;写在前面 &#x1f64b;‍♂️大家好呀&#xff0c;我是超梦。大家可以叫我小梦~ 又到了练习SQL的时候啦&#xff01;一起来学习吧&#xff01; &#x1f64b;‍♂️ 小伙伴们如果在学习过程中有不明白的地方&#xff0c;欢迎评论区留言提问&#xff0c;小梦定知无…

阿里一面 —— 什么是多线程?

马上就要到金三银四佳季了&#xff0c;是找工作的好时候&#xff0c;小伙伴们一定要把握好时机&#xff0c;找到心仪的高薪工作。找工作就少不了面试&#xff0c;那我们从现在开始&#xff0c;多刷刷面试题&#xff0c;查缺补漏&#xff01;&#xff01;&#xff01; 目录 ⭐什…

第九节:JWT简介和以JS+WebApi为例基于JWT的安全校验

一. 简介 1. 背景 传统的基于Session的校验存在诸多问题&#xff0c;比如&#xff1a;Session过期、服务器开销过大、不能分布式部署、不适合前后端分离的项目。 传统的基于Token的校验需要存储Key-Value信息&#xff0c;存在Session或数据库中都有弊端&#xff0c;如果按照一…

赢在CSDN —— 我们一起向前

赢在CSDN&#xff0c;一起向前&#xff01; ⭐初遇CSDN ⭐我也成为了一名创作博主 ⭐如何在CSDN赚到一桶金 ⭐CSDN带给自己的成长 ⭐对CSDN的期待 ⭐个人创作的规划 ⭐初遇CSDN 不知不觉来到CSDN已经有1379天的时间啦&#xff0c;时间过得好快呀&#xff01;记得刚踏进CS…

volatile关键字的作用-适用场景

volatile在Java并发编程中常用于保持内存可见性和防止指令重排序。内存可见性&#xff08;Memory Visibility&#xff09;&#xff1a;所有线程都能看到共享内存的最新状态&#xff1b;防止指令重排&#xff1a;在基于偏序关系的Happens-Before内存模型中&#xff0c;指令重排技…

MySQL你掌握了多少?这些SQL题你能作对几道?

MySQL是工作中常用数据库&#xff0c;必须掌握&#xff0c;但小伙伴们又掌握了多少呢&#xff0c;今天一起来测试一下吧~ 力扣SQL ⭐组合两个表 ⭐第二高的薪水 ⭐超过经理收入的员工 ⭐查找重复的电子邮箱 ⭐从不订购的客户 ⭐大的国家 ⭐删除重复的电子邮箱 ⭐有趣的…

mybatis配置文件加注释报错怎么办?改一笔就能帮你解决

今天配置mybatis写了一个小demo&#xff0c;本以为顺顺利利的就完成了&#xff0c;没想到就报了如下错误。直接emo了。 java.lang.ExceptionInInitializerErrorat com.lm.learn.dao.UserTest.getUsers(UserTest.java:14)at sun.reflect.NativeMethodAccessorImpl.invoke0(Nativ…

第十二节:深究内核模式锁的使用场景(自动事件锁、手动事件锁、信号量、互斥锁、读写锁、动态锁)

一. 整体介绍 温馨提示&#xff1a;内核模式锁&#xff0c;在不到万不得已的情况下&#xff0c;不要使用它&#xff0c;因为代价太大了&#xff0c;有很多种替代方案。 内核模式锁包括&#xff1a; ①&#xff1a;事件锁 ②&#xff1a;信号量 ③&#xff1a;互斥锁 ④&#xf…

玩转Mybatis —— 一个小demo,带你快速入门Mybatis

目录 &#x1f91e;Mybatis官网介绍 &#x1f91e;Mybatis安装 &#x1f91e;Mybatis核心配置文件 &#x1f91e;构建 SqlSessionFactory &#x1f91e;获取 SqlSession &#x1f91e;通过 XML 定义已映射的 SQL 语句 &#x1f91e;作用域&#xff08;Scope&#xff09;…

MySQL掌握的怎么样?听说这几道MySQL面试题没有几个人能答得出来

MySQL可谓是程序员必备技能&#xff0c;怎么检测自己掌握了多少呢&#xff0c;一起来测试一下吧&#xff01;一共12个关卡&#xff0c;看看你能闯到第几关&#xff1f; 目录 什么是左外链接&#xff0c;什么是右外链接&#xff1f; 分页关键字是什么&#xff1f; Select 语句…

IIS/ASP.NET 管道

ASP.NET MVC 是建立在 ASP.NET 平台上基于 MVC 模式的 Web 应用框架&#xff0c;深刻理解 ASP.NET MVC 的前提是对 ASP.NET 管道式设计具有深刻的认识。由于 ASP.NET Web 应用大都寄宿于 IIS 上&#xff0c;将两者结合起来了解在 IIS 和 ASP.NET 管道中是如何流动的。 IIS5.x与…

【JAVA知识点每日一练】 —— 运算符的那些事

都说基础不牢地动山摇&#xff0c;还真是那么回事&#xff01;别小看这运算符&#xff0c;每个语法中运算符扮演者举足轻重的角色&#xff0c;稍微没用对&#xff0c;那可就犯了大错误&#xff01; 接下来跟随小梦&#xff0c;迈着轻松且愉快的步伐一起看看运算符的那些事吧~ 运…

ASP.NET Core管道深度剖析[共4篇]

在《管道是如何处理HTTP请求的&#xff1f;》中&#xff0c;我们对ASP.NET Core的请求处理管道的构成以及它对请求的处理流程进行了详细介绍&#xff0c;接下来我们需要了解的是这样一个管道是如何被构建起来的。这样一个管道由一个服务器和一个HttpApplication构成&#xff0c…

SQL为什么动不动就百行以K记?

发明SQL的初衷之一显然是为了降低人们实施数据查询计算的难度。SQL中用了不少类英语的词汇和语法&#xff0c;这是希望非技术人员也能掌握。确实&#xff0c;简单的SQL可以当作英语阅读&#xff0c;即使没有程序设计经验的人也能运用。 然而&#xff0c;面对稍稍复杂的查询计算…

深入理解happens-before和as-if-serial语义

本文大部分整理自《Java并发编程的艺术》&#xff0c;温故而知新&#xff0c;加深对基础的理解程度。下面可以和小编来一起学习下 概述 本文大部分整理自《Java并发编程的艺术》&#xff0c;温故而知新&#xff0c;加深对基础的理解程度。 指令序列的重排序 我们在编写代码…

开源SPL重新定义OLAP Server

OLAP&#xff08;Online Analytical Processing&#xff09;是指在线联机分析&#xff0c;基于数据查询计算并实时获得返回结果。日常业务中的报表、数据查询、多维分析等一切需要即时返回结果的数据查询任务都属于OLAP的范畴。对应的&#xff0c;行业内也有相应产品来满足这类…

平层、错层、跃层、复式、loft的区别是什么?

平层正如字面上的理解&#xff0c;是所有功能厅都在同一个水平面上。平时我们所见的户型&#xff0c;都是平层。错层室内各功能用房在不同的平面上&#xff0c;用2-4个台阶进行隔断。跃层是两层的住宅&#xff0c;在室内会设计一条楼梯连接上下两层&#xff0c;功能区会分开。复…

C#集合类型总结和性能分析

C#集合类型概述 集合是.NET FCL(Framework Class Library)中很重要的一部分。所有的集合类都继承自IEnumerable。集合类总体可分为一下几类&#xff1a;关联/非关联型集合&#xff0c;顺序/随机访问集合&#xff0c;顺序/无序集合&#xff0c;泛型/非泛型集合&#xff0c;线程…