`
yangshen998
  • 浏览: 1288767 次
文章分类
社区版块
存档分类
最新评论

我所知道的.NET异步

 
阅读更多

对于异步,相信大家都不十分陌生。准确点来说就是方法执行后立即返回,待到执行完毕会进行通知。就是当一个任务在执行的时候,尤其是需要耗费很长的时间进行处理的任务,如果利用单线程进行操作的话,势必造成界面的阻塞;而利用异步方式,则不会出现这种情况。 区别于同步处理,可以说阻塞的异步其实就相当于同步。

同步方式的实现

先来看一个同步的例子:

假设现在我们需要导入文本文件的内容,然后对文件内容做处理。那么这就需要分为两步来进行,第一步是导入文本内容,我们利用函数A表示;第二部就是处理文本,我们利用函数B来表示。假设现在A不执行完,B不能进行。而且由于文本内容非常大,导入需要十几到几十分钟不等,那么我们得提示用户导入进度,这里就涉及到了界面交互问题。利用同步方式来做,效果如何呢?首先请看运行效果:

其实上面的图片是我运行了一段时间的程序的截图,但是由于作用在了同步模式下,导致界面阻塞,从而产生极差的用户体验。

代码如下:

View Code
 #region 第一步:加载进入内存
        private void ReadIntoMemory()
        {
            if (String.IsNullOrEmpty(fileName))
            {
                MessageBox.Show("文件名不能为空!");
                return;
            }

            string result;
            long mainCount = 0;
            using (StreamReader sr = new StreamReader(fileName, Encoding.Default))
            {
                while ((result = sr.ReadLine()) != null)
                {
                    mainCount++;

                    recordList.Add(result); //添加记录到List中存储,以便在下一步进行处理。

                    double statusResult = (double)mainCount / (double)totalCount;

                    lblCurrentRecords.Text = mainCount.ToString();
                    lblStatus.Text = statusResult.ToString("p");
                    pbMain.Value = Int32.Parse((Math.Floor(statusResult)*100).ToString());
                }
            }
        }
        #endregion

        #region 第二步:处理数据
        private void ProcessRecords()
        {
            if (recordList ==null)
            {
                throw new Exception("数据不存在!");
            }

            if (recordList.Count==0)
            {
                return;
            }

            int childCount = 0;
            int recordCount = recordList.Count;

            for (int i = 0; i < recordCount; i++)
            {
                string thisRecord=recordList[i];
                if (String.IsNullOrEmpty(thisRecord) || !thisRecord.Contains(","))
                {
                    return;
                }

                string[] result = thisRecord.Split(',');
               
                ListViewItem lvi = new ListViewItem(result[0]);

                for (int j = 1; j < result.Length; j++)
                {
                    lvi.SubItems.Add(result[j]);
                }
                listItem.Add(lvi);

                childCount++;
                double percentage = (double)childCount / (double)recordCount;
                pbChild.Value = Int32.Parse((Math.Floor(percentage) * 100).ToString());
            }
        }
        #endregion

那么我们是如何运行的呢:

        #region 开始进行处理
        private void btnLoad_Click(object sender, EventArgs e)
        {
            GetTotalRecordNum(); //得到总条数

            ReadIntoMemory();
            ProcessRecords();
        }
        #endregion
复制代码

看到了没,我们是直接顺序运行的。之所以出现上面的情况,最主要就是界面处理和后台处理均糅合在了同一个线程之中,这样当后台进行数据处理的时候,会造成前台UI线程无法更新UI。要解决这种情况,当然是使用异步方式类处理。

那么在.net编程中,有哪几种模式可以实现异步呢?

4种异步方式

  1. ThreadPool.QueueUserworkItem实现
  2. APM模式(就是BeginXXX和EndXXX成对出现。)
  3. EAP模式(就是Event based, 准确说来就是任务在处理中或者处理完成,会抛出事件)
  4. Task

上面总共4种方式中,其中在.net 2.0中常用的是(1),(2),(3),而在.net 4.0中支持的是(4),注意(4)在.net 2.0中是不能使用的,因为不存在。

首先来说说ThreadPool.QueueUserWorkItem方式,也是最简单的一种方式。

系统将需要运行的任务放到线程池中,那么线程池中的任务就有机会通过并行的方式进行运行。

其次来说说APM模式

这种模式非常常见,当然也是Jeff Richter极力推荐的一种方式。同时我也是这种模式的粉丝。这种模式的使用非常简单,就是利用Begin***的方式将需要进行异步处理的任务放入,然后通过End***的方式来接受方法的返回值。同时在Begin***和End***任务进行的过程中,如果涉及到界面UI的更新的时候,我们完全可以加入通知的功能。

在Begin***和End***进行处理的时候,传递的是IAsyncResult对象,这种对象在Begin***中会承载一个委托对象,然后在End***中进行还原并得到返回值。

如果你在设计的时候,需要有多个方法用到异步,并且想控制他们的运行顺序,请参考ManualResetEvent 和 AutoResetEvent方法,他们均是通过设置信号量来进行同步的。

下面来看一个例子:

假设现在我们需要导入文本文件的内容,然后对文件内容做处理。那么这就需要分为两步来进行,第一步是导入文本内容,我们利用函数A表示;第二部就是处理文本,我们利用函数B来表示。假设现在A不执行完,B不能进行。而且由于文本内容非常大,导入需要十几到几十分钟不等,那么我们得提示用户导入进度,这里就涉及到了界面交互问题。利用APM模式如何来做呢?首先请看运行效果:

代码如下:

  #region 典型的APM处理方式,利用Action作为无参无返回值的委托
        private void BeginReadIntoMemory()
        {
            Action action = new Action(ReadIntoMemory);
            action.BeginInvoke(new AsyncCallback(EndReadIntoMemory), action);
        }

        private void EndReadIntoMemory(IAsyncResult iar)
        {
            Action action = (Action)iar.AsyncState;
            action.EndInvoke(iar);
        }

        private void BeginProcessRecords()
        {
            Action action = new Action(ProcessRecords);
            action.BeginInvoke(new AsyncCallback(EndProcessRecords), action);
        }

        private void EndProcessRecords(IAsyncResult iar)
        {
            Action action = (Action)iar.AsyncState;
            action.EndInvoke(iar);
        }
        #endregion
复制代码

我们是如何调用的呢:

        #region 开始进行处理,需要通过ManualResetEvent设置xinhaoilang的方式进行同步
        private void btnLoad_Click(object sender, EventArgs e)
        {
            GetTotalRecordNum(); //得到总条数

            BeginReadIntoMemory(); //读取数据到内存
            BeginProcessRecords(); //处理数据内容
        }
        #endregion
复制代码

在上面的代码段中,APM模式的处理方式很明显,Begin×××和End×××成对出现,这种方式使用简便,所以很推荐。并且如果涉及到顺序执行的情况,请参加我的前一篇文章:浅谈C#中常见的委托

然后来说说EAP模式

这种模式也很常见,准确来说就是在系统中通过申明委托事件,然后在执行过程中或者执行完毕后抛出事件。最常见的莫过于WebClient类的DownloadStringCompleted事件,这里我们将使用BackgroundWorker来进行讲解,虽然它本身就能够实现异步操作。在这里,我们只是用到了一个从文本中读取大数据量到内存的操作。图示如下:

这里是进行中的操作:

这里是撤销后的操作:

那么是如何实现的呢?我们先从BackgroundWorker注册的几个事件说起:

首先是DoWork事件,他的注册方式如下:

bgWorker.DoWork += new DoWorkEventHandler(worker_DoWork);

这个主要是用来开启任务的:

 private void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;

            ReadIntoMemory(worker, e); //开始工作
        }
复制代码

然后就是ProgressChanged事件,注册方式如下:

         bgWorker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);

从字面上就知道是进行进度报告:

private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            pbMain.Value = e.ProgressPercentage; //利用PrograssBar报告导入进度
        }
复制代码

最后就是任务完成报告,注册方式为:

 bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);

这里可以进行错误捕获以及任务取消方面的处理:

  private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message);
            }
            else if (e.Cancelled)
            {
                tsInfo.Text = "Data Loading Canceled...";
            }
            else
            {
                tsInfo.Text = "Data Loading Completed...";
            }
        }
复制代码

当然,这个组件在函数运行的过程中,需要向组件传送当前进度的信息,并且在运行过程中,需要检测任务有没有被取消,以达到自动取消任务的功能:

View Code
#region 第一步:加载数据到内存
        private void ReadIntoMemory(BackgroundWorker worker, DoWorkEventArgs e)
        {

            if (String.IsNullOrEmpty(fileName))
            {
                MessageBox.Show("文件名不能为空!");
                return;
            }

            string result;
            long mainCount = 0;
            using (StreamReader sr = new StreamReader(fileName, Encoding.Default))
            {
                while ((result = sr.ReadLine()) != null)
                {
                    mainCount++;

                    recordList.Add(result); //添加记录到List中存储,以便在下一步进行处理。

                    double statusResult = (double)mainCount / (double)totalCount;
                    syncContext.Send(new SendOrPostCallback((s) =>
                    {
                        if (worker.CancellationPending) //检测到用户取消任务
                        {
                            e.Cancel = true;  //任务取消
                        }
                        else
                        {
                            lblCurrentRecords.Text = mainCount.ToString();
                            lblStatus.Text = statusResult.ToString("p");
                            int thisPercentange = Int32.Parse((Math.Floor(statusResult * 100)).ToString());
                            //pbMain.Value = thisPercentange;
                            worker.ReportProgress(thisPercentange); //报告当前的进度
                            tsNotify.Text = "| 当前导入";
                        }
                    }), null);

                }
            }
        }
        #endregion

再说说利用task的实现的方式

 关于Task类,可以说在4.0之前从来没有见过,使用起来非常的简单,也很方便。其实,对于Task类,我也是参考了诸多文章,下面的这句话,引用自另外一篇文章:

Task在并行计算中的作用很凸显,首次构造一个Task对象时,他的状态是Created。以后,当任务启动时,他的状态变成WaitingToRun。Task在一个线程上运行时,他的状态变成Running。任务停止运行,并等待他的任何子任务时,状态变成WaitingForChildrenToComplete。任务完全结束时,它进入以下三个状态之一:RanToCompletion,Canceled或者Faulted。一个Task<TResult>运行完成时,可通过Task<TResult>的Result属性来查询任务的结果,一个Task或者Task<TResult>出错时,可以查询Task的Exception属性来获得任务抛出的未处理的异常,该属性总是返回一个AggregateException对象,他包含所有未处理的异常。
为简化代码,Task提供了几个只读的Boolean属性,IsCanceled,IsFaulted,IsCompleted。注意,当Task处于RanToCompleted,Canceled或者Faulted状态时,IsCompleted返回True。为了判断一个Task是否成功完成,最简单的方法是if(task.Status == TaskStatus.RanToCompletion)。

当然,我们还是以上面的例子来进行编程与讲解。

首先,我们要开启一个Task,那么Task taskOne = new Task(ReadIntoMemory);表示将ReadIntoMemory函数注册成为了任务来运行,然后利用taskOne.Start();来开启任务。那么如何运行第二个任务,并且还要等到第一个运行完成之后呢? 这里我们就需要用到其ContinueWith方法:

Task taskTwo = taskOne.ContinueWith(Action => { ProcessRecords(); });

这样,就行了那么当运行的时候,程序的确会按照顺序来启动任务。图示和APM模式中的图片相同,我就不贴了,下面是代码:

            Task taskOne = new Task(ReadIntoMemory);
            taskOne.Start();
            Task taskTwo = taskOne.ContinueWith(Action => { ProcessRecords(); });
复制代码

Task<TResult>泛型方法中的TResult为返回值类型,承载的是一个无参,但是有返回值的任务。所以传入的函数要么是有一个参数带返回值的;要么就是无参数带返回值的,要么就是无参数无返回值的。如果是一个参数,有返回值的话,可以利用下面的方式来进行:

Task<int> taskOne = new Task<int>(a=>ReadIntoMemory((int)a),5);
0
0
分享到:
评论

相关推荐

    c#.net异步机制

    C#.NET异步机制是.NET框架中用于提升应用程序性能的关键特性,它允许代码在等待I/O操作(如网络通信或磁盘读写)完成时,不阻塞主线程执行其他任务,从而提高程序的响应性和效率。在.NET Framework 4.0及更高版本中...

    asp.net 异步分页

    ASP.NET异步分页是一种高效的数据展示技术,尤其在处理大量数据时,能显著提高网页的响应速度和用户体验。在传统的同步分页中,每次页面加载都会请求整个数据集,导致服务器负担加重,页面响应时间增加。而异步分页...

    ajax.net异步调用

    **Ajax.NET 异步调用详解** Ajax.NET 是 .NET Framework 中实现 AJAX(Asynchronous JavaScript and XML)技术的一种方式,它允许开发人员在不刷新整个网页的情况下更新页面的部分内容,从而提供更流畅、更快捷的...

    asp.net异步刷新案例

    ASP.NET异步刷新技术是Web开发中的一个重要概念,它允许页面的部分内容在不重新加载整个页面的情况下进行更新,显著提高了用户体验。在这个“qq聊天工具异步刷新案例”中,我们将探讨如何利用ASP.NET实现类似即时...

    ASP.NET异步调用

    在深入探讨“ASP.NET异步调用”这一主题前,我们先来理解其核心概念以及在实际场景中的应用。标题“ASP.NET异步调用”指的是在ASP.NET框架下,利用Ajax技术实现前后端的异步通信过程。具体而言,这里的异步调用主要...

    asp.net 异步 上传控件(带进度条)

    ASP.NET异步上传控件是Web开发中常见的一种功能,特别是在处理大文件或者批量上传时,用户界面的体验显得尤为重要。这里的"asp.net 异步 上传控件(带进度条)"指的是一个专为ASP.NET平台设计的控件,它支持异步上传...

    asp.net异步上传小文件

    在ASP.NET中,异步上传小文件是一种提高用户体验的技术,特别是在用户需要上传多个小型文件时。这种方式避免了页面刷新,使得上传过程更加流畅。本文将详细介绍如何实现ASP.NET中的异步文件上传,并讨论相关的前端...

    AsynchDownload_visualbasic_vb.net异步下载_

    总结来说,这个VB.NET异步下载实例展示了如何利用`WebClient`类进行非阻塞下载,通过事件处理程序更新UI,以及如何使用ListView控件来管理和显示下载任务的状态。这样的设计对于创建具有用户友好的下载管理功能的...

    .net分布式异步队列等待技术

    《.NET分布式异步队列等待技术》是一个深入讲解如何在C#环境下实现高效、可靠的分布式异步处理的教程。本教程聚焦于如何利用.NET框架的特性,构建能够处理大量并发请求的分布式队列系统,以提升应用的性能和可扩展性...

    C# Asp.Net 邮件异步发送

    本主题将深入探讨如何使用C#实现Asp.NET中的邮件异步发送,包括同步和异步两种方法,以及使用SMTP(Simple Mail Transfer Protocol)模式进行实际的邮件传输。 首先,我们需要了解SMTP。SMTP是一种Internet标准,...

    asp.net异步调用后台方法提交

    ### ASP.NET 异步调用后台方法提交 在ASP.NET开发中,为了提升用户体验和页面响应速度,异步调用后台方法是一种常见的技术手段。本文将详细介绍如何在ASP.NET中实现异步调用后台方法,并解释相关的代码实现细节。 ...

    ASP.Net异步页面的经典示例代码

    这个代码是关于asp.net做异步处理的。没有用ajax,而是用了一些类。 程序中有三个页面,也即三个示例: 示例1.演示异步获取一个网址的内容,处理后显示在OutPut这一Label上 示例2:演示如何异步从数据库查询数据,并...

    .net异步调用后台函数

    - `Bin`目录则包含编译后的.aspx页面和引用的库文件,如DLLs,它们提供了异步操作所需的功能,如.NET Framework库,自定义业务逻辑类库等。 7. **异步调用的挑战与最佳实践**: 异步调用虽然能提升性能,但也需要...

    ASP.NET实现异步上传文件Demo

    ASP.NET实现异步上传文件是一项常见的Web开发任务,它能够显著提升用户体验,因为用户无需等待整个文件上传完成就可以继续操作其他页面元素。在本Demo中,我们将深入探讨如何使用ASP.NET来实现这一功能。 首先,...

    .Net异步编程(async await)初探

    针对.net异步编程(async await)使用入门的一些讲解

    jquey,zTree插件,asp.net下异步加载数据实例

    jquey,zTree插件,asp.net下异步加载数据实例,项目中要用到jquery,zTree插件,网上没有查到可以直接运行的asp.net实例, 做了一个,可以直接运行,刚开始学这个插件的可以下来参考

    ajax .net 异步请求方式

    综上所述,.NET为开发者提供了丰富的工具和方法来实现AJAX异步请求,无论是在传统的ASP.NET Web Forms、MVC,还是现代的Web API和Blazor框架中,都能找到合适的方式实现高效、无刷新的交互体验。在实际项目中,...

    .Net异步Socket服务器代码

    在.NET框架中,异步Socket编程是构建高性能网络应用程序的核心技术。它允许程序在等待网络I/O操作完成时,不阻塞主线程,从而提高应用的响应性和效率。本篇文章将详细探讨如何使用.NET实现一个异步Socket服务器,...

    ASP.NET中异步加载数据的无刷新Tab源代码

    异步加载数据的核心是利用AJAX(Asynchronous JavaScript and XML),尽管XML现在已经被JSON所取代。ASP.NET提供了内置的AJAX支持,通过ASP.NET AJAX库(包括UpdatePanel、ScriptManager等控件)实现无刷新更新。...

    .NET平台上的异步编程

    异步编程是构建响应灵敏的界面和高可扩展性的服务端应用的关键,但是异步编程却又是困难的。为此微软在F#中和.NET未来版本中都不遗余力的提高异步编程的体验,本PPT主要谈论.NET平台上异步编程的演化。

Global site tag (gtag.js) - Google Analytics