一、引言

  在上两个专题中我为大家介绍.NET 1.0中的APM和.NET 2.0中的EAP,在使用前面两种模式进行异步编程的时候,大家多多少少肯定会感觉到实现起来比较麻烦, 首先我个人觉得,当使用APM的时候,首先我们要先定义用来包装回调方法的委托,这样难免有点繁琐, 然而使用EAP的时候,我们又需要实现Completed事件和Progress事件,上面两种实现方式感觉都有点繁琐,同时微软也意思到了这点,所以在.NET 4.0中提出了一个新的异步模式——基于任务的异步模式,该模式主要使用System.Threading.Tasks.Task和Task<T>类来完成异步编程,相对于前面两种异步模式来讲,TAP使异步编程模式更加简单(因为这里我们只需要关注Task这个类的使用),同时TAP也是微软推荐使用的异步编程模式,下面就具体为大家分享下本专题的内容.

二、什么是TAP——基于任务的异步模式介绍

基于任务的异步模式(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的异步方法来进一步理解基于任务的异步模式。

三、如何使用TAP——使用基于任务的异步模式来异步编程

看完上面的介绍,我们是不是很迫不及待想知道如何自己实现一个基于任务的异步模式的异步方法的,并且希望只需要这个方法就可以完成异步操作的取消和进度报告的功能的(因为EAP中需要实现其他的事件和定义事件参数类型,这样的实现未免过于复杂),下面就基于上专题中实现的程序用基于任务的异步模式来完成下。下面就让我们实现自己的异步方法(亮点为只需要一个方法就可以完成进度报告和异步操作取消的功能):

        //  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 = ;
byte[] bufferBytes = new byte[bufferSize];
try
{
request = (HttpWebRequest)WebRequest.Create(url);
if (DownloadSize != )
{
request.AddRange(DownloadSize);
}
response = (HttpWebResponse)request.GetResponse();
responseStream = response.GetResponseStream();
int readSize = ;
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, , bufferBytes.Length);
if (readSize > )
{
DownloadSize += readSize;
int percentComplete = (int)((float)DownloadSize / (float)totalSize * );
filestream.Write(bufferBytes, , 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);
}
}
        // Start DownLoad File
private void btnStart_Click(object sender, EventArgs e)
{
filestream = new FileStream(downloadPath, FileMode.OpenOrCreate);
this.btnStart.Enabled = false;
this.btnPause.Enabled = true;
filestream.Seek(DownloadSize, SeekOrigin.Begin);
// 捕捉调用线程的同步上下文派生对象
sc = SynchronizationContext.Current;
cts = new CancellationTokenSource();
// 使用指定的操作初始化新的 Task。
task = new Task(() => Actionmethod(cts.Token), cts.Token);
// 启动 Task,并将它安排到当前的 TaskScheduler 中执行。
task.Start();
//await DownLoadFileAsync(txbUrl.Text.Trim(), cts.Token,new Progress<int>(p => progressBar1.Value = p));
}
// 任务中执行的方法
private void Actionmethod(CancellationToken ct)
{
// 使用同步上文文的Post方法把更新UI的方法让主线程执行
DownLoadFile(txbUrl.Text.Trim(), ct, new Progress<int>(p =>
{
sc.Post(new SendOrPostCallback((result)=>progressBar1.Value=(int)result),p);
}));
}
// Pause Download
private void btnPause_Click(object sender, EventArgs e)
{
// 发出一个取消请求
cts.Cancel();
}

四、TAP与APM或EAP可以转换吗?——与其他异步模式的转换

从上面的程序代码我们可以清楚的发现——基于任务的异步模式确实比前面的两种异步模式更加简单使用,所以,从.NET Framework 4.0开始,微软推荐使用TAP来实现异步编程,这里就涉及之前用APM或EAP实现的程序如何迁移到用TAP实现的问题的,同时.NET Framwwork对他们之间的转换了也做了很好的支持。

4.1 将APM转换为TAP

在System.Threading.Tasks命名空间中,有一个TaskFactory(任务工程)类,我们正可以利用该类的FromAsync方法来实现将APM转换为TAP,下面就用基于任务的异步模式来实现在异步编程模型博文中例子。

        // 大家可以对比这两种实现方式
#region 使用APM实现异步请求
private void APMWay()
{
WebRequest webRq = WebRequest.Create("http://msdn.microsoft.com/zh-CN/");
webRq.BeginGetResponse(result =>
{
WebResponse webResponse = null;
try
{
webResponse = webRq.EndGetResponse(result);
Console.WriteLine("请求的内容大小为: " + webResponse.ContentLength);
}
catch (WebException ex)
{
Console.WriteLine("异常发生,异常信息为: " + ex.GetBaseException().Message);
}
finally
{
if (webResponse != null)
{
webResponse.Close();
}
}
}, null);
}
#endregion
#region 使用FromAsync方法将APM转换为TAP
private void APMswitchToTAP()
{
WebRequest webRq = WebRequest.Create("http://msdn.microsoft.com/zh-CN/");
Task.Factory.FromAsync<WebResponse>(webRq.BeginGetResponse, webRq.EndGetResponse, null, TaskCreationOptions.None).
ContinueWith(t =>
{
WebResponse webResponse = null;
try
{
webResponse = t.Result;
Console.WriteLine("请求的内容大小为: " + webResponse.ContentLength);
}
catch (AggregateException ex)
{
if (ex.GetBaseException() is WebException)
{
Console.WriteLine("异常发生,异常信息为: " + ex.GetBaseException().Message);
}
else
{
throw;
}
}
finally
{
if (webResponse != null)
{
webResponse.Close();
}
}
});
}
#endregion

上面代码演示了使用APM的原始实现方式以及如何使用FromAsync方法把APM的实现方式转换为TAP的实现方法,把这两种方式放在一起,一是可以帮助大家做一个对比,使大家更容易明白APM与TAP的转换,二是大家也可以通过上面的对比明白TAP与APM的区别。

4.2 将EAP转化为TAP

处理APM可以升级为用TAP来实现外,对于EAP,我们同样可以对其转换为TAP的方式,下面代码演示了如何将EAP转换为TAP的实现方式:

#region 将EAP转换为TAP的实现方式

            // webClient类支持基于事件的异步模式(EAP)
WebClient webClient = new WebClient(); // 创建TaskCompletionSource和它底层的Task对象
TaskCompletionSource<string> tcs = new TaskCompletionSource<string>(); // 一个string下载好之后,WebClient对象会应发DownloadStringCompleted事件
webClient.DownloadStringCompleted += (sender, e) =>
{
// 下面的代码是在GUI线程上执行的
// 设置Task状态
if (e.Error != null)
{
// 试图将基础Tasks.Task<TResult>转换为Tasks.TaskStatus.Faulted状态
tcs.TrySetException(e.Error);
}
else if (e.Cancelled)
{
// 试图将基础Tasks.Task<TResult>转换为Tasks.TaskStatus.Canceled状态
tcs.TrySetCanceled();
}
else
{
// 试图将基础Tasks.Task<TResult>转换为TaskStatus.RanToCompletion状态。
tcs.TrySetResult(e.Result);
}
}; // 当Task完成时继续下面的Task,显示Task的状态
// 为了让下面的任务在GUI线程上执行,必须标记为TaskContinuationOptions.ExecuteSynchronously
// 如果没有这个标记,任务代码会在一个线程池线程上运行
tcs.Task.ContinueWith(t =>
{
if (t.IsCanceled)
{
Console.WriteLine("操作已被取消");
}
else if (t.IsFaulted)
{
Console.WriteLine("异常发生,异常信息为:" + t.Exception.GetBaseException().Message);
}
else
{
Console.WriteLine(String.Format("操作已完成,结果为:{0}", t.Result));
}
}, TaskContinuationOptions.ExecuteSynchronously); // 开始异步操作
webClient.DownloadStringAsync(new Uri("http://msdn.microsoft.com/zh-CN/"));
#endregion

基于任务的编程模型TAP的更多相关文章

  1. 基于SOA的编程模型

    1.webservice是SOA架构的一种实现 ============================================================================ ...

  2. 第九节:深究并行编程Parallel类中的三大方法 (For、ForEach、Invoke)和几大编程模型(SPM、APM、EAP、TAP)

    一. 并行编程 1. 区分串行编程和串行编程 ①. 串行编程:所谓的串行编程就是单线程的作用下,按顺序执行.(典型代表for循环 下面例子从1-100按顺序执行) ②. 并行编程:充分利用多核cpu的 ...

  3. linux网络编程模型

    1.编程模型 Linux网络编程模型是基于socket的编程模型

  4. (2)LoraWAN:Lora LMIC library 编程模型及API

    二.LMIC library 编程模型及API LMiC库可以通过一组API函数(API functions),运行时函数(run-time functions),回调函数(callback func ...

  5. 第03讲:Flink 的编程模型与其他框架比较

    Flink系列文章 第01讲:Flink 的应用场景和架构模型 第02讲:Flink 入门程序 WordCount 和 SQL 实现 第03讲:Flink 的编程模型与其他框架比较 本课时我们主要介绍 ...

  6. 云巴:基于MQTT协议的实时通信编程模型

    概要 有人常问,云巴实时通信系统到底提供了一种怎样的服务,与其他提供推送或 IM 服务的厂商有何本质区别.其实,从技术角度分析,云巴与其它同类厂商都是面向开发者的通信服务,宏观的编程模型都是大同小异, ...

  7. atitit.基于组件的事件为基础的编程模型--服务器端控件(1)---------服务器端控件和标签之间的关系

    atitit.基于组件的事件为基础的编程模型--服务器端控件(1)---------服务器端控件和标签之间的关系 1. server控件是要server了解了标签.种类型的server控件: 1 1. ...

  8. MapReduce编程模型详解(基于Windows平台Eclipse)

    本文基于Windows平台Eclipse,以使用MapReduce编程模型统计文本文件中相同单词的个数来详述了整个编程流程及需要注意的地方.不当之处还请留言指出. 前期准备 hadoop集群的搭建 编 ...

  9. (1)线程的同步机制 (2)网络编程的常识 (3)基于tcp协议的编程模型

    1.线程的同步机制(重点)1.1 基本概念 当多个线程同时访问同一种共享资源时可能会造成数据的覆盖和不一致等问题,此时就需要对线程之间进行协调和通信,该方式就叫线程的同步机制. 如: 2003年左右 ...

随机推荐

  1. Android WiFi热点完全研究(自定义创建、跳转系统界面设置、读取配置、切换,Android6.0适配)

    前言: WiFi热点设置页面的安全性选项在Android 4.x上有“无”.“WPA PSK”.“WPA2 PSK”三个选项,在Android 5.0(含)之后去掉了WPA PSK选项(部分手机厂家会 ...

  2. Android Studio ( Linux) 创建模拟器报错

    Linux下Android studio创建模拟器最后一步报错 报错:An error occurred while creating the AVD. See idea.log for detail ...

  3. Google浏览器“无法添加来自此网站的应用、扩展程序和应用脚本”的解决办法

    原文链接:https://blog.csdn.net/Fan_Weibin/article/details/80402790 解决方法如下: 在桌面找到Google Chrome图标→右击属性→在快捷 ...

  4. Ubuntu Server 18.04 LTS 安装

    版本:Ubuntu Server 18.04.1 LTS 环境:VMware Workstation 14 Player 下载地址:https://www.ubuntu.com/download/se ...

  5. 企业级任务调度框架Quartz(1) --企业应用中的任务调度介绍

    由于目前的工作内容为建行CLPM批处理业务的设计工作,所以很好的理解批处理所用的任务调度框架Quartz势在必行:为了能够更好的去服务于工作,也 为了提升自己,所以我学习了Quartz Job Sch ...

  6. Javase范式

    package Xwxx; import java.util.ArrayList; import java.util.Iterator; import java.util.function.IntBi ...

  7. day25-2 random,os,sys模块

    目录 random 为什么要有random模块,random模块有什么用 os 为什么要有os模块,os模块有什么用 sys 为什么要有sys模块,sys模块有什么用 random import ra ...

  8. PHP中each与list用法分析

    1.each的用法 先看API array each ( array &$array ) api里是这么描述的:each — 返回数组中当前的键/值对并将数组指针向前移动一步 我们先来看看返回 ...

  9. 安装`lrzsz`包及其报错解决办法

    rz命令的安装包名是lrzsz. 安装lrzsz包时报错Failed to mount cd:///?devices=/dev/sr1,/dev/sr0 on /var/adm/mount/AP_0x ...

  10. Disconf使用简单Demo

    创建配置文件 在敲Demo之前,需要在Disconf上创建自己的APP,然后在APP的某个环境下创建配置文件,如下面截图中的流程,这里就简单创建了一个redis.properties,内容是redis ...