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

一、引言

  在上一个专题中为大家介绍了.NET 1.0中提出来的异步编程模式——APM,虽然APM为我们实现异步编程提供了一定的支持,同时它也存在着一些明显的问题——不支持对异步操作的取消和没有提供对进度报告的功能,对于有界面的应用程序来说,进度报告和取消操作的支持也是必不可少的,既然存在这样的问题,微软当然也应该提供给我们解决问题的方案了,所以微软在.NET 2.0的时候就为我们提供了一个新的异步编程模型,也就是我这个专题中介绍的基于事件的异步编程模型——EAP。

实现了基于事件的异步模式的类将具有一个或者多个以Async为后缀的方法和对应的Completed事件,并且这些类都支持异步方法的取消、进度报告和报告结果。

当我们调用实现基于事件的异步模式的类的 XxxAsync方法时,即代表开始了一个异步操作,该方法调用完之后会使一个线程池线程去执行耗时的操作,所以当UI线程调用该方法时,当然也就不会堵塞UI线程了。

二、深入剖析BackgroundWorker组件类

在深入讲解BackgroundWorker类之前,让我们先看看BackgroundWorker类具有的成员和对应的介绍的(这里只列出一些在异步编程中经常使用的属性和方法,具体关于该类成员可以查看MSDN——BackgroundWorker):


BackgroundWorker类


公共属性


属性名


说明


CancellationPending


获取一个值,指示应用程序是否已请求取消后台操作


IsBusy


获取一个值,指示 BackgroundWorker是否正在运行异步操作。


WorkReportsProgress


获取或设置一个值,该值指示 BackgroundWorker能否报告进度更新。


WorkerSupportsCancellation


获取或设置一个值,该值指示 BackgroundWorker是否支持异步取消。


公共方法


名称


说明


CancelAsync


请求取消挂起的后台操作。


ReportProgress


引发 ProgressChanged 事件(官方这样解释我就要信?)


RunWorkerAsync


开始执行后台操作。


公共事件


名称


说明


DoWork


调用 RunWorkerAsync 时发生(官方是这么解释的,你想知道为什么调用RunWorkerAsync方法就会触发DoWork事件吗?)


ProgressChanged


调用ReportProgress时发生(官方是这么解释的,你想知道为什么调用ReportProgress方法就会触发ProgressChanged事件吗?)


RunWorkerCompleted


当后台操作已完成、被取消或引发异常时发生。

分析为什么调用RunWorkerAsync方法就会触发DoWorker事件?

// RunWorkerAsync的源码什么都没有做,只是调用了该方法的重载方法RunWorkerAsync(objectargument)方法
publicvoidRunWorkerAsync()

{

    this.RunWorkerAsync(null);

}

// 下面就看看RunWorkerAsync带有一个参数的重载方法的源码

publicvoidRunWorkerAsync(object argument)

{

    if (this.isRunning)

    {

        thrownewInvalidOperationException(SR.GetString("BackgroundWorker_WorkerAlreadyRunning"));

    }

   //这个方法把一些私有字段赋值

   //这些赋值是为了我们使用isBusy公共属性来检查BackgroundWorker组件是否在运行异步操作

//和检查公共属性 CancellationPending属性来检查异步操作是否取消

    this.isRunning=true;

    this.cancellationPending=false;

    //AsyncOperation类是通过获得调用线程的同步上下文来实现跨线程访问,这个实现在APM专题中我们是自己通过代码来实现的,然而实现EAP的类在内容帮我们实现了,这样就不需要我们自己去解决这个问题了,从中也可以看出EAP的实现是基于APM的,只是实现EAP的类帮我们做了更多的背后的事情

    this.asyncOperation= AsyncOperationManager.CreateOperation(null);

    //这里就是我们上一专题中介绍的使用委托实现的异步编程部分

   // 我们在EAP的类中调用了BeginInvoke方法,从而也可以证明EAP是基于APM的,所以APM的介绍很有必要。

    this.threadStart.BeginInvoke(argument,null,null);

}

1.    我们从上面的代码可以看到调用RunWorkerAsync方法就是调用threadStart委托,我们要知道RunWorkerAsync方法到底背后发生了什么事情,就首先需要知道threadStart委托包装了哪个方法?并且需要知道委托在什么地方实例化的?
2.    委托什么地方实例化话的?谈到实例化当然大家首先想到的就是构造函数了,不错,我们就看看BackgroundWorker构造函数:
// 这里查看构造函数都是因为前面的分析
// 从构造函数中我们可以确实可以看到threadStart委托是这里初始化的
public BackgroundWorker()
{
    // 初始化threadStart委托
    this.threadStart=new WorkerThreadStartDelegate(this.WorkerThreadStart);
    // 这里也初始化了操作完成委托和进度报告委托
    this.operationCompleted= new SendOrPostCallback(this.AsyncOperationCompleted);
    this.progressReporter=new SendOrPostCallback(this.ProgressReporter);
}
3. 从构造函数中已经知道threadStart包装了WorkerThreadStart方法,从而解决了第一步的疑惑,接下来就让我们看看WorkerThreadStart方法的代码:
privatevoidWorkerThreadStart(object argument)
{
    objectresult =null;
    Exceptionerror =null;
    boolcancelled =false;
    try
    {
       DoWorkEventArgs e =newDoWorkEventArgs(argument);
       //该方法中又是调用了onDoWork方法
       //
        this.OnDoWork(e);
        if(e.Cancel)
        {
           cancelled =true;
        }
        else
        {
           result = e.Result;
        }
    }
    catch(Exception exception2)
    {
        error= exception2;
    }
    //这里也解释了操作完成时会触发Completed事件
    // 分析过程和调用RunWorkerAsync方法触发DoWork事件类似
    RunWorkerCompletedEventArgs arg =newRunWorkerCompletedEventArgs(result, error, cancelled);
    this.asyncOperation.PostOperationCompleted(this.operationCompleted,arg);
}

4. 上面的代码中可以知道WorkerThreadStart调用了受保护的OnDoWork方法,下面就让我们看看OnDoWork方法的代码,到这里我们离事物的本质已经不远了。
// OnDoWork的源码
protectedvirtualvoidOnDoWork(DoWorkEventArgs e)
{
    //从事件集合中获得委托对象
   DoWorkEventHandler handler = (DoWorkEventHandler)base.Events[doWorkKey];
    if(handler !=null)
    {
        //调用委托,也就是调用注册DoWork事件的方法
        // 我们在使用BackgroundWorker对象的时候,首先需要对它的DoWork事件进行注册
        //到这里就可以解释为什么调用RunWorkerAsync方法会触发DoWork事件了
       handler(this, e);
    }
}
// 当我们使用+=符号对DoWork事件进行注册时,背后调用确实Add方法,具体可以查看我的事件专题。
publiceventDoWorkEventHandler DoWork
{
    add
    {
        //把注册的方法名添加进一个事件集合中
        // 这个事件集合也是类似一个字典,doWorkKey是注册方法的key,通过这个key就可以获得包装注册方法的委托
        base.Events.AddHandler(doWorkKey,value);
    }
    remove
    {
        base.Events.RemoveHandler(doWorkKey,value);
    }
}
从上面的代码中的注释我们可以解释一开始的疑惑,并且也更好地解释了事件特性,关于事件,你也可以参看我的事件专题(事件也是委托,归根究底又是委托啊,从而可见委委托是多么的重要,同时建议大家在理解委托的时候,可以根据后面的特性重复地去理解)。
对于开始表格中提出的其他的几个疑惑的分析思路和这个分析思路类似,大家可以按照这个思路自己去深入理解下BackgroundWorker类,这里我就不多解释了。相信大家通过上面我的分析可以很快解决其他几个疑惑的,如果你完全理解上面的分析相信你会对EAP,委托和事件又有进一步的理解。
四、使用BackgroundWorker组件进行异步编程
剖析完了BackgroundWorker组件之后,我们是不是很想看看如何使用这个类来实现异步编程呢?下面向大家演示一个使用BackgroundWorker组件实现异步下载文件的一个小程序,该程序支持异步下载(指的就是用线程池线程要执行下载操作),断点续传、下载取消和进度报告的功能,通过这个程序,相信大家也会对基于事件的异步模式有一个更好的理解和知道该模式可以完成一些什么样的任务,下面就看看该程序的主要代码的(因为代码中都有详细的解释,这里就不多解释代码的实现了):
//Begin Start Download file or Resume the download
        privatevoidbtnDownload_Click(object sender, EventArgs e)
        {
            if(bgWorkerFileDownload.IsBusy !=true)
            {
                   // Start the asynchronous operation
                   // Fire DoWork Event
                   bgWorkerFileDownload.RunWorkerAsync();
                   // Create an instance of the RequestState
                   requestState =newRequestState(downloadPath);
                   requestState.filestream.Seek(DownloadSize, SeekOrigin.Begin);
                   this.btnDownload.Enabled =false;
                   this.btnPause.Enabled =true;
            }
            else
            {
               MessageBox.Show("正在执行操作,请稍后");
            }
        }
        //Pause Download
        privatevoidbtnPause_Click(object sender, EventArgs e)
        {
            if(bgWorkerFileDownload.IsBusy&&bgWorkerFileDownload.WorkerSupportsCancellation==true)
            {
               // Pause the asynchronous operation
               // Fire RunWorkerCompleted event
               bgWorkerFileDownload.CancelAsync();
            }
        }
        #regionBackGroundWorker Event
        //Occurs when RunWorkerAsync is called.
        privatevoidbgWorkerFileDownload_DoWork(object sender,DoWorkEventArgs e)
        {
            // Getthe source of event
            BackgroundWorker bgworker = senderasBackgroundWorker;
            try
            {
               // Do the DownLoad operation
               // Initialize an HttpWebRequest object
               HttpWebRequest myHttpWebRequest =(HttpWebRequest)WebRequest.Create(txbUrl.Text.Trim());
               // If the part of the file have been downloaded,
               // The server should start sending data from the DownloadSize to the endof the data in the HTTP entity.
               if (DownloadSize !=0)
               {
                   myHttpWebRequest.AddRange(DownloadSize);
               }
               // assign HttpWebRequest instance to its request field.
               requestState.request = myHttpWebRequest;
               requestState.response =(HttpWebResponse)myHttpWebRequest.GetResponse();
               requestState.streamResponse = requestState.response.GetResponseStream();
               int readSize =0;
               while (true)
               {
                   if (bgworker.CancellationPending ==true)
                   {
                       e.Cancel = true;
                       break;
                   }
                   readSize = requestState.streamResponse.Read(requestState.BufferRead,0,requestState.BufferRead.Length);
                   if (readSize >0)
                   {
                       DownloadSize +=readSize;
                       intpercentComplete = (int)((float)DownloadSize/ (float)totalSize * 100);
                       requestState.filestream.Write(requestState.BufferRead,0,readSize);
                       // 报告进度,引发ProgressChanged事件的发生
                       bgworker.ReportProgress(percentComplete);
                   }
                   else
                   {
                       break;
                   }
               }
            }
            catch
            {
               throw;
            }
        }
        //Occurs when ReportProgress is called.
        privatevoidbgWorkerFileDownload_ProgressChanged(object sender,ProgressChangedEventArgs e)
        {
            this.progressBar1.Value= e.ProgressPercentage;
        }
        //Occurs when the background operation has completed, has been canceled, or hasraised an exception.
        privatevoidbgWorkerFileDownload_RunWorkerCompleted(objectsender, RunWorkerCompletedEventArgs e)
        {
            if(e.Error !=null)
            {
               MessageBox.Show(e.Error.Message);
               requestState.response.Close();
            }
            elseif(e.Cancelled)
            {
                MessageBox.Show(String.Format("下载暂停,下载的文件地址为:{0}\n已经下载的字节数为: {1}字节",downloadPath, DownloadSize));
                requestState.response.Close();
                requestState.filestream.Close();
                this.btnDownload.Enabled =true;
                this.btnPause.Enabled =false;
            }
            else
            {
               MessageBox.Show(String.Format("下载已完成,下载的文件地址为:{0},文件的总字节数为: {1}字节",downloadPath, totalSize));
               this.btnDownload.Enabled =false;
               this.btnPause.Enabled =false;
               requestState.response.Close();
               requestState.filestream.Close();
            }
        }
        #endregion
        // GetTotal Size of File
        privatevoidGetTotalSize()
        {
            HttpWebRequest myHttpWebRequest =(HttpWebRequest)WebRequest.Create(txbUrl.Text.Trim());
            HttpWebResponse response =(HttpWebResponse)myHttpWebRequest.GetResponse();
            totalSize = response.ContentLength;
            response.Close();
        }
 //This class stores the State of the request.
    publicclassRequestState
    {
        publicintBufferSize =2048;
        publicbyte[]BufferRead;
        publicHttpWebRequest request;
        publicHttpWebResponse response;
        publicStream streamResponse;
        publicFileStream filestream;
        publicRequestState(string downloadPath)
        {
           BufferRead =newbyte[BufferSize];
           request =null;
           streamResponse =null;
           filestream =new FileStream(downloadPath,FileMode.OpenOrCreate);
        }
    }

原文地址:https://www.cnblogs.com/zhlziliaoku/p/9217225.html

时间: 2024-10-15 20:36:16

异步编程(二)基于事件的异步编程模式 (EAP)的相关文章

C#中的异步调用及异步设计模式(三)——基于事件的异步模式

四.基于事件的异步模式(设计层面) 基于事件的C#异步编程模式是比IAsyncResult模式更高级的一种异步编程模式,也被用在更多的场合.该异步模式具有以下优点: ·                  “在后台”执行耗时任务(例如下载和数据库操作),但不会中断您的应用程序. ·                  同时执行多个操作,每个操作完成时都会接到通知(在通知中可以区分是完成了哪个操作). ·                  等待资源变得可用,但不会停止(“挂起”)您的应用程序. ·  

C#中的异步调用及异步设计模式(二)——基于 IAsyncResult 的异步设计模式

三.基于 IAsyncResult 的异步设计模式(设计层面) IAsyncResult 异步设计模式通过名为 BeginOperationName 和 EndOperationName 的两个方法来实现原同步方法的异步调用,如 FileStream 类提供了 BeginRead 和 EndRead 方法来从文件异步读取字节,它们是 Read 方法的异步版本 Begin 方法包含同步方法签名中的任何参数,此外还包含另外两个参数:一个AsyncCallback 委托和一个用户定义的状态对象.委托用

MSDN搬运 之 [基于事件的异步模式]

基于事件的异步模式概述 那些同时执行多项任务.但仍能响应用户交互的应用程序通常需要实施一种使用多线程的设计方案.System.Threading 命名空间提供了创建高性能多线程应用程序所必需的所有工具,但要想有效地使用这些工具,需要有丰富的使用多线程软件工程的经验.对于相对简单的多线程应用程序,BackgroundWorker 组件提供了一个简单的解决方案.对于更复杂的异步应用程序,请考虑实现一个符合基于事件的异步模式的类. 基于事件的异步模式具有多线程应用程序的优点,同时隐匿了多线程设计中固有

【温故知新】C#基于事件的异步模式(EAP)

在开发winform和调用asp.net的web service引用的时候,会出现许多命名为 MethodNameAsync 的方法. 例如: winform的按钮点击 this.button1.Click += new System.EventHandler(this.button1_Click); private void button1_Click(object sender, EventArgs e) { //dosomething } 这就是基于事件的异步编程模式,它实现了不影响主线程

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

一.引言 在上一个专题中为大家介绍了.NET 1.0中提出来的异步编程模式--APM,虽然APM为我们实现异步编程提供了一定的支持,同时它也存在着一些明显的问题--不支持对异步操作的取消和没有提供对进度报告的功能,对于有界面的应用程序来说,进度报告和取消操作的支持也是必不可少的,既然存在这样的问题,微软当然也应该提供给我们解决问题的方案了,所以微软在.NET 2.0的时候就为我们提供了一个新的异步编程模型,也就是我这个专题中介绍的基于事件的异步编程模型--EAP. 二.介绍 实现了基于事件的异步

基于事件的异步模式(EAP)

什么是EAP异步编程模式 EAP基于事件的异步模式是.net 2.0提出来的,实现了基于事件的异步模式的类将具有一个或者多个以Async为后缀的方法和对应的Completed事件,并且这些类都支持异步方法的取消.进度报告和报告结果.然而.net中并不是所有的类都支持EAP,总结起来有以下17个类支持EAP异步. System.Object的派生类型: System.Activies.WorkflowInvoke System.Deployment.Application.ApplicationD

在Silverlight中的DispatcherTimer的Tick中使用基于事件的异步请求

需求:在silverlight用户界面上使用计时器定时刷新数据. 在 Silverlight 中的 DispatcherTimer 的 Tick 事件 中使用异步请求数据时,会出现多次请求的问题,以下是ViewModel的代码,看样子没什么问题: using System; using System.Net; using System.Threading; using System.Windows; using System.Windows.Controls; using System.Wind

基于事件的异步模式——BackgroundWorker

实现异步处理的方法很多,经常用的有基于委托的方式,今天记录的是基于事件的异步模式.利用BackgroundWorker组件可以很轻松的实现异步处理,并且该组件还支持事件的取消.进度报告等功能.本文以计算两个数X.Y的和为例. 程序界面如下图,其中三个文本框分别为两个加数和处理结果,两个按钮为计算和取消,按钮下方为进度条. 引入BackgroundWorker组件,为DoWork.ProgressChanged.RunWorkerCompleted三个事件指定方法.将WorkerReportsPr

Android异步处理二:使用AsyncTask异步更新UI界面

Android异步处理二:使用AsyncTask异步更新UI界面 - lzc的专栏 - 博客频道 - CSDN.NET 在<Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面>中,我们使用Thread+Handler的方式实现了异步更新UI界面,这一篇中,我们介绍一种更为简洁的实现方式:使用AsyncTask异步更新UI界面. 概述: AsyncTask是在Android SDK 1.5之后推出的一个方便编写后台线程与UI线程交互的辅助类.AsyncTask的