广告
返回顶部
首页 > 资讯 > 后端开发 > ASP.NET >.NET异步编程模式的三种类型介绍
  • 682
分享到

.NET异步编程模式的三种类型介绍

2024-04-02 19:04:59 682人浏览 独家记忆
摘要

一、引言 .net中很多的类、接口在设计的时候都考虑了多线程问题,简化了多线程程序的开发,不用自己去写WaitHandler等这些底层的代码,由于历史的发展,这些类的接口设计有着三种

一、引言

.net中很多的类、接口在设计的时候都考虑了多线程问题,简化了多线程程序的开发,不用自己去写WaitHandler等这些底层的代码,由于历史的发展,这些类的接口设计有着三种不同的风格:EAP、APM和TPL。目前重点用TPL。

二、EAP

EAP是Event-based Asynchronous Pattem(基于事件的异步模型)的简写,类似于ajax中的XmlHttpRequest,send之后并不是处理完成了,而是在onreadystatechange事件中再通知处理完成。看下面的一个示例。

我们创建一个winform程序,窗体上面有一个按钮和一个文本框,点击按钮开始下载,下载完成以后给文本框赋值,界面如图所示:

开始下载代码如下:

using System;
using System.Net;
using System.windows.FORMs;

namespace EAPDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 开始下载
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnDownload_Click(object sender, EventArgs e)
        {
            // 实例化对象
            WEBClient wc = new WebClient();
            // 注册完成事件
            wc.DownloadStrinGCompleted += Wc_DownloadStringCompleted;
            // 异步下载 不会阻塞界面,窗体可以随意拖动
            wc.DownloadStringAsync(new Uri("http://www.baidu.com"));
        }

        /// <summary>
        /// 下载完成事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            // 给文本框赋值
            txtContent.Text = e.Result;
        }
    }
}

程序执行的时候,界面可以随意拖动,不会卡住界面,而使用同步方法就会卡住界面,使用同步方法时放到一个单独的线程里面也可以实现不卡住界面,但是那种写法比较复杂,直接使用异步方法就可以完成。 

EAP的优点是简单,缺点是当实现复杂的业务的时候很麻烦,比如下载A成功后再下载B,如果下载B成功再下载C,否则就下载D。

EAP的类的特点是:一个异步方法配合一个***Completed事件。.NET中基于EAP的类比较少,也有更好的替代品,因此了解即可。

三、APM

APM(Asynchronous Programming Model)的缩写,是.NET旧版本中广泛使用的异步编程模型。使用了APM的异步方法会返回一个IAsyncResult 对象,这个对象有一个重要的属性:AsyncWaitHandle。它是一个用来等待异步任务执行结束的一个同步信号。看下面的一个示例。

界面上由一个按钮和一个文本框,点击按钮开始读取文件内容,读取完毕之后给文本框赋值,界面如下图所示:

按钮代码如下:

using System;
using System.io;
using System.Text;
using System.Windows.Forms;

namespace APMDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnRead_Click(object sender, EventArgs e)
        {
            // 打开文件
            FileStream fs = File.OpenRead("F:/test.txt");
            // 声明数组
            byte[] buffer = new byte[1024];
            // BeginRead开始读取
            IAsyncResult aResult = fs.BeginRead(buffer, 0, buffer.Length, null, null);
            //等待任务执行结束
            aResult.AsyncWaitHandle.WaitOne();
            this.txtContent.Text = Encoding.UTF8.GetString(buffer);
            // 结束读取
            fs.EndRead(aResult);
        }
    }
}

因为里面使用了WaitOne(),这里会产生等待,如果等待时候过长,还是会产生界面卡顿,所以最好还是放到多线程里面去执行,修改后的代码如下:

using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace APMDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnRead_Click(object sender, EventArgs e)
        {
            //// 打开文件
            //FileStream fs = File.OpenRead("F:/test.txt");
            //// 声明数组
            //byte[] buffer = new byte[1024];
            //// BeginRead开始读取
            //IAsyncResult aResult = fs.BeginRead(buffer, 0, buffer.Length, null, null);
            ////等待任务执行结束
            //aResult.AsyncWaitHandle.WaitOne();
            //this.txtContent.Text = Encoding.UTF8.GetString(buffer);
            //// 结束读取
            //fs.EndRead(aResult);

            #region 多线程
            ThreadPool.QueueUserWorkItem(state => 
            {
                // 打开文件
                FileStream fs = File.OpenRead("F:/test.txt");
                // 声明数组
                byte[] buffer = new byte[1024];
                // BeginRead开始读取
                IAsyncResult aResult = fs.BeginRead(buffer, 0, buffer.Length, null, null);
                //等待任务执行结束
                aResult.AsyncWaitHandle.WaitOne();
                this.txtContent.BeginInvoke(new Action(() => 
                {
                    this.txtContent.Text= Encoding.UTF8.GetString(buffer); ;
                }));
                // 结束读取
                fs.EndRead(aResult);
            });
            #endregion
        }
    }
}

如果不加 aResult.AsyncWaitHandle.WaitOne() 那么很有可能打印出空白,因为 BeginRead只是“开始读取”。调用完成一般要调用 EndXXX 来回收资源。

APM 的特点是:方法名字以 BeginXXX 开头,返回类型为 IAsyncResult,调用结束后需要EndXXX。

.Net 中有如下的常用类支持 APM:Stream、sqlCommand、Socket 等。

APM 还是太复杂,了解即可。

四、TPL

TPL(Task Parallel Library)是.NET 4.0之后带来的新特性,更简洁,更方便。现在在.NET平台下已经被广泛的使用。我们看下面的一个示例。

界面有一个开始按钮和一个文本框,点击按钮开始读取文件内容,读取完毕赋值到文本框中,开始按钮代码如下:

using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TPLDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private  void btnRead_Click(object sender, EventArgs e)
        {
            // 打开文件
            using (FileStream fs = File.OpenRead("F:/test.txt"))
            {
                // 声明数组
                byte[] buffer = new byte[1024];
                // BeginRead开始读取
                Task<int> task = fs.ReadAsync(buffer, 0, buffer.Length);
                // 等待
                task.Wait();
                this.txtContent.Text = Encoding.UTF8.GetString(buffer);
            }
        }
    }
}

执行task.Wait()的时候如果比较耗时,也会造成界面卡顿,所以最好还是放到一个单独的线程中取执行,优化后的代码如下:

using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TPLDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private  void btnRead_Click(object sender, EventArgs e)
        {
            //// 打开文件
            //using (FileStream fs = File.OpenRead("F:/test.txt"))
            //{
            //    // 声明数组
            //    byte[] buffer = new byte[1024];
            //    // BeginRead开始读取
            //    Task<int> task = fs.ReadAsync(buffer, 0, buffer.Length);
            //    // 等待
            //    task.Wait();
            //    this.txtContent.Text = Encoding.UTF8.GetString(buffer);
            //}


            // 开启一个线程
            ThreadPool.QueueUserWorkItem(state =>
            {
                // 打开文件
                using (FileStream fs = File.OpenRead("F:/test.txt"))
                {
                    // 声明数组
                    byte[] buffer = new byte[1024];
                    // BeginRead开始读取
                    Task<int> task = fs.ReadAsync(buffer, 0, buffer.Length);
                    // 等待
                    task.Wait();
                    this.txtContent.Invoke(new Action(() =>
                    {
                        this.txtContent.Text = Encoding.UTF8.GetString(buffer);
                    }));
                }
            });
        }
    }
}

这样用起来和APM相比的好处是:不需要EndXXX。但是TPL还有更强大的功能,那就是异步方法,我们在界面上在增加一个按钮用来演示使用异步方法,其实现代码如下:

using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TPLDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private  void btnRead_Click(object sender, EventArgs e)
        {
            //// 打开文件
            //using (FileStream fs = File.OpenRead("F:/test.txt"))
            //{
            //    // 声明数组
            //    byte[] buffer = new byte[1024];
            //    // BeginRead开始读取
            //    Task<int> task = fs.ReadAsync(buffer, 0, buffer.Length);
            //    // 等待
            //    task.Wait();
            //    this.txtContent.Text = Encoding.UTF8.GetString(buffer);
            //}


            // 开启一个线程
            ThreadPool.QueueUserWorkItem(state =>
            {
                // 打开文件
                using (FileStream fs = File.OpenRead("F:/test.txt"))
                {
                    // 声明数组
                    byte[] buffer = new byte[1024];
                    // BeginRead开始读取
                    Task<int> task = fs.ReadAsync(buffer, 0, buffer.Length);
                    // 等待
                    task.Wait();
                    this.txtContent.Invoke(new Action(() =>
                    {
                        this.txtContent.Text = Encoding.UTF8.GetString(buffer);
                    }));
                }
            });
        }

        /// <summary>
        /// 异步读取方法
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void btnAsync_Click(object sender, EventArgs e)
        {
            // 打开文件
            using (FileStream fs = File.OpenRead("F:/test.txt"))
            {
                // 声明数组
                byte[] buffer = new byte[1024];
                // BeginRead开始读取
                await fs.ReadAsync(buffer, 0, buffer.Length);
                this.txtContent.Text = Encoding.UTF8.GetString(buffer);
            }
        }
    }
}

使用异步方法,要把方法标记为异步的,就是将方法标记为async,然后方法中使用await,await表示等待ReadAsync执行结束。

注意:方法中如果使用了await,那么方法就必须标记为async,但也不是所有方法都可以被轻松的标记为async。WinForm中的事件处理方法都可以被标记为async,mvc中的Action方法也可以被标记为async,控制台的Main方法则不能被标记为async。

TPL的特点是:方法都以XXXAsync结尾,返回值类型是泛型的Task<T>。

TPL让我们可以用线性的方式去编写异步程序,不在需要像EAP中那样搞一堆回调、逻辑跳来跳去了。await现在已经被javascript借鉴走了!

我们用await实现下面的一个功能:先下载A,如果下载的内容长度大于100则下载B,否则下载C。代码实现如下:

using System;
using System.Net;
using System.Windows.Forms;

namespace AwaitDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private async void btnDownload_Click(object sender, EventArgs e)
        {
            WebClient wc = new WebClient();
            string str=await wc.DownloadStringTaskAsync("http://www.baidu.com");
            if(str.Length>100)
            {
                str = await wc.DownloadStringTaskAsync("https://www.jd.com/");
            }
            else
            {
                str = await wc.DownloadStringTaskAsync("http://www.dangdang.com/");
            }
            this.txtContent.Text = str;
        }
    }
}

Task<T>中的T是什么类型在每个方法中都不一样,具体是什么类型要看文档。

WebClient、Stream、Socket等这些“历史悠久”的类都同时提供了APM、TPL更改的api,甚至有的还提供了EAP风格的API。这里建议尽可能的使用TPL风格的。

五、如何编写异步方法

在上面的示例中,我们都是使用的.NET框架给我们提供好的API,如果我们想自己编写异步方法该怎么办?因为目前Task使用最广泛,所以我们这里以TPL为例讲解如何编写自己的异步方法。

首先异步方法的返回值要是Task<T>类型,方法里面返回一个Task类型,潜规则(不要求)是方法名字以Async结尾,这样就会知道这是一个异步方法。看下面的例子:

/// <summary>
/// 自定义异步方法,返回类型是string
/// </summary>
/// <returns></returns>
private Task<string> TestTask()
{
    return Task.Run<string>(() => 
    {
        // 让线程休眠3秒,模拟一个耗时的操作
        Thread.Sleep(3000);
        return "这是一个异步方法";
    });
}

这样就编写好了一个异步方法。那么怎么使用这个异步方法呢?使用方法跟我们使用.NET框架提供的异步方法一样,看下面调用异步方法的代码:

using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace AwaitDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private async void btnDownload_Click(object sender, EventArgs e)
        {
            WebClient wc = new WebClient();
            string str=await wc.DownloadStringTaskAsync("http://www.baidu.com");
            if(str.Length>100)
            {
                str = await wc.DownloadStringTaskAsync("https://www.jd.com/");
            }
            else
            {
                str = await wc.DownloadStringTaskAsync("http://www.dangdang.com/");
            }
            this.txtContent.Text = str;
        }

        /// <summary>
        /// 自定义异步方法,返回类型是string
        /// </summary>
        /// <returns></returns>
        private Task<string> TestTask()
        {
            return Task.Run<string>(() => 
            {
                // 让线程休眠3秒,模拟一个耗时的操作
                Thread.Sleep(3000);
                return "这是一个异步方法";
            });
}

        /// <summary>
        /// 调用自定义的异步方法
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void btnCall_Click(object sender, EventArgs e)
        {
            string str = await TestTask();
            this.txtContent.Text = str;
        }
    }
}

程序输出结果:

这样就可以调用自己写的异步方法了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程网。

--结束END--

本文标题: .NET异步编程模式的三种类型介绍

本文链接: https://www.lsjlt.com/news/143266.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • .NET异步编程模式的三种类型介绍
    一、引言 .NET中很多的类、接口在设计的时候都考虑了多线程问题,简化了多线程程序的开发,不用自己去写WaitHandler等这些底层的代码,由于历史的发展,这些类的接口设计有着三种...
    99+
    2022-11-13
  • C#异步编程的三种模式
    使用异步编程,方法调用是在后台运行(通常在线程和任务的帮助下),并且不会阻塞调用线程。异步编程有三种模式:异步模式,基于事件的异步模式和基于任务的异步模式(TAP)。 一.异步模式 ...
    99+
    2022-11-13
  • 深入解析ASP、API、NPM三种异步编程方式的优缺点
    ASP、API、NPM是三种常用的异步编程方式。在现代Web应用程序中,异步编程已经成为了不可或缺的一部分。异步编程可以极大地提高应用程序的性能和响应速度。在本文中,我们将。 一、ASP ASP(Active Server Pages)是一...
    99+
    2023-09-17
    api npm 异步编程
  • 数据类型和异步编程:Java中响应式编程的最佳实践
    Java是一门广泛应用于企业级应用程序开发的编程语言。Java语言的强类型特性对于数据类型的定义和处理有着很好的支持。而异步编程技术则可以更好地处理大规模数据和复杂业务逻辑。本篇文章将介绍数据类型和异步编程在Java中的最佳实践,以帮助Ja...
    99+
    2023-10-21
    数据类型 响应 异步编程
  • Java中的数据类型:如何在响应式编程中使用异步?
    随着互联网技术的不断发展,响应式编程逐渐成为了一种主流的编程模型。在响应式编程中,异步编程是一项非常重要的技术,能够帮助我们更好地处理高并发的场景,提高程序的性能和稳定性。而在Java中,数据类型的选择也会影响到我们在响应式编程中使用异步...
    99+
    2023-10-21
    数据类型 响应 异步编程
  • Java中的响应式编程:如何利用异步方式处理不同的数据类型?
    响应式编程是一种处理异步和事件驱动程序的编程范式,它可以极大地提高程序的性能和可扩展性。Java中的响应式编程是一种基于流的编程方式,可以让我们更加高效地处理不同类型的数据。在本文中,我们将介绍Java中的响应式编程及其使用。 一、什么是...
    99+
    2023-10-21
    数据类型 响应 异步编程
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作