异步编程


1)异步编程的重要性

  在C#5.0中提供了关键字:async和await

  使用异步编程后台运行方法调用,程序的运行过程中就不会一直处于等待中。便于用户继续操作.

  异步编程有3种模式:异步模式、基于事件的模式、基于任务的模式。

  基于任务的模式就使用了关键字。

2)异步模式

public delegate int AddHandler(int a,int b);
public class 加法类
{ public static int Add(int a, int b)
{
Console.WriteLine("开始计算:" + a + "+" + b);
Thread.Sleep();
Console.WriteLine("计算完成!");
return a + b;
} } 

  2.1)同步调用  

    先调用,待处理

    

static void Main()
{
Console.WriteLine("===== 同步调用 SyncInvokeTest =====");
AddHandler handler = new AddHandler(加法类.Add);
int result = handler.Invoke(, );
Console.WriteLine("继续做别的事情。。。");
Console.WriteLine(result);
Console.ReadKey();
}

  2.2)异步模式APM(Asynchronous Programming Model)

    先处理,待调用

    异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。

    异步模式定义了开始调用方法和结束调用方法BeginInvoke和EndInvoke
    开始调用方法接受同步方法的所有输入参数
    结束调用方法使用同步的所有输出参数,按照同步方法返回类型返回结果。
    开始调用方法中包含了一个参数。该参数用于接受异步方法执行完成后调用的委托。
    开始调用方法返回异步结果接口IAsyncResult,用于验证调用是否已经完成,并且一直等到方法的执行结束。

static void Main()
{
Console.WriteLine("===== 异步调用 AsyncInvokeTest =====");
AddHandler handler = new AddHandler(加法类.Add);
//IAsyncResult: 异步操作接口(interface)
//BeginInvoke: 委托(delegate)的一个异步方法的开始
IAsyncResult result = handler.BeginInvoke(, , null, null);
Console.WriteLine("继续做别的事情。。。");
//异步操作返回
Console.WriteLine(handler.EndInvoke(result));
Console.ReadKey();
}

异步模式实现了先进行所有事务的处理,最后等待处理的结果一起反映给客户端。

  2.3)基于事件的异步模式EAP(Event-based Asynchronous Pattern)

    基于事件的C#异步编程模式是比IAsyncResult模式更高级的一种异步编程模式   

    该异步模式具有以下优点:

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

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

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

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

    基于事件的异步模式需要以下三个类型的支持

      AsyncOperation:

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

      AsyncOperationManager:

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

      WindowsFormsSynchronizationContext:

      该类继承自SynchronizationContext类型,该类型是基于事件异步模式通信的核心,该类型解决了“保证SendOrPostCallback委托在UI线程上执行”的问题

      

static void Main(string[] args)
{
object userState = "check";
System.Collections.Specialized.HybridDictionary userStateToLifetime = new System.Collections.Specialized.HybridDictionary();
AsyncOperation asyncOp = System.ComponentModel.AsyncOperationManager.CreateOperation(userState); lock (userStateToLifetime.SyncRoot)
{
if (userStateToLifetime.Contains(userState))
{
throw new ArgumentException("同一时间不同IP操作过多,出现了并发", "操作状态");
} userStateToLifetime[userState] = asyncOp;
}
Action<object> eventHandler = (e) =>
{
userStateToLifetime[userState] = null;
var arr = new List<int>();
for (int i = ; i < ; i++)
{
arr.Add(i);
}
Console.WriteLine("2.执行系统事件:"+ asyncOp.UserSuppliedState);
asyncOp.PostOperationCompleted(new System.Threading.SendOrPostCallback(e as Action<object>), userState);
}; Action<object> onSelectedChange = (e) =>
{ Console.WriteLine("3.执行自定义事件开始" + asyncOp.UserSuppliedState);
if (userStateToLifetime[userState] == null)
{
Console.WriteLine("4.执行系统事件时被取消了");
return;
}
Console.WriteLine("5.执行自定义事件结束" + asyncOp.UserSuppliedState);
asyncOp.OperationCompleted();
};
asyncOp.Post(new System.Threading.SendOrPostCallback(eventHandler), onSelectedChange);
Console.WriteLine("1.做其他的事情");
Console.ReadKey();
}

基于事件的异步模式与普通的异步模式不同点的是,基于事件的异步可以在不同异步方法中串联,可以通过对象状态通知跟踪挂起的操作,取消挂起的操作,接收进度更新和增量结果。

因此我们需要对多个异步方法进行互通消息时,使用事件的异步模式,一般的使用BeginInvoke和EndInvoke实现好了.

一般基于事件的异步方法以Async为方法签名后缀

  2.4)基于任务的异步模式TAP(Task-based Asynchronous Pattern)

我们如果看到有类的方法名后缀以TaskAsync结尾,那就是基于任务的异步模式了。

基于任务的异步模式同样也支持异步操作的取消和进度的报告的功能。

基于任务的异步模式使用Task类实现。还提供了关键字async和await

async异步执行

await等待任务返回

使用关键字async和await实现同步调用:

static void Main(string[] args)
{
Console.WriteLine("主线程测试开始..");
AsyncMethod();
Thread.Sleep();
Console.WriteLine("主线程测试结束..");
Console.ReadLine();
} static async void AsyncMethod()
{
Console.WriteLine("开始异步代码");
var result = await MyMethod();
Console.WriteLine("异步代码执行完毕");
} static async Task<int> MyMethod()
{
for (int i = ; i < ; i++)
{
Console.WriteLine("异步执行" + i.ToString() + "..");
await Task.Delay(); //模拟耗时操作
}
return ;
}

从代码中可以看出.await和async关键字带来的新优势.

对于运行结果来看,主线程正常操作,await将延迟操作持续响应.持续响应完毕后输出异步代码执行完毕。

我们需要了解的一点是:
使用 await 的非同步方法必须由 async 关键字修改,await 关键字声明的任务方法必须具有返回值,并且返回值是Task类型的,方法中如果使用了await关键字则,方法必须声明为异步的,必须用async进行声明.

使用async可以将方法、lamdba表达式、委托声明为异步的.

3)异步编程基础

下面我们将使用Task类来创建异步编程.

  3.1)创建任务

    基于任务的异步模式指定,在异步方法名后加上Async后缀,并返回一个任务,返回的是一个Task类型,看下面示例:

//  同步方法
static string Greeting(string name)
{
Console.WriteLine("开始Greeting");
Thread.Sleep();
return string.Format("Hello, {0}", name);
}
//  异步方法
static Task<string> GreetingAsync(string name)
{
return Task.Run<string>(() =>
{
Console.WriteLine("开始GreetingAsync");
return Greeting(name);
});
}

  3.2)调用异步方法

使用await关键字调用返回任务的异步方法,使用await关键字需要有async修饰符声明的方法
async修饰符只能用于返回Task或void方法,await只能用于返回task的方法

//  异步方法调用
private async static void CallerWithAsync()
{
Console.WriteLine("started CallerWithAsync in thread {0} and task {1}", Thread.CurrentThread.ManagedThreadId, Task.CurrentId);
string 结果 = await GreetingAsync("Stephanie");
Console.WriteLine(结果);
Console.WriteLine("finished GreetingAsync in thread {0} and task {1}", Thread.CurrentThread.ManagedThreadId, Task.CurrentId);
}

  3.3)延续任务

Task类的ContinueWith方法定义了任务完成后调用的代码,ContinueWith方法的委托参数接收已完成的任务作为参数传入,使用Result属性访问任务返回结果

//  延续任务ContinueWith
private static void CallerWithContinuationTask()
{
Console.WriteLine("开始 CallerWithContinuationTask in thread {0} and task {1}", Thread.CurrentThread.ManagedThreadId, Task.CurrentId); var t1 = GreetingAsync("Stephanie"); t1.ContinueWith(t =>
{
string 结果 = t.Result;
Console.WriteLine(结果);
Console.WriteLine("finished CallerWithContinuationTask in thread {0} and task {1}", Thread.CurrentThread.ManagedThreadId, Task.CurrentId);
}); }

  3.4)同步上下文

使用async和await关键字,当await完成之后,不需要处理,就能访问ui线程,默认生成的代码会把线程转换到同步上下文的线程中

  3.5)使用多个异步方法

一个异步方法中可以调用一个或多个异步方法.

1.使用await按顺序调用异步方法

//  顺序调用异步方法
private async static void MultipleAsyncMthods()
{
string s1 = await GreetingAsync("Stephanie");
string s2 = await GreetingAsync("Matthias");
Console.WriteLine("运行 所有方法.\n 结果 1: {0}\n 结果 2: {1}", s1, s2);
}
//  GreetingAsync异步方法第2次完全独立第一次调用的结果

2.使用组合器

如果异步方法不依赖于其他方法,每个异步方法都不使用await,而是把每个异步方法的返回结果赋值给Task变量,运行会更快

一个组合器可以接受多个同一类型的参数,并返回同一类型的值。多个同一类型的参数被组合成一个参数来传递。Task组合器接受多个Task对象作为参数,返回一个Task

private async static void MultipleAsyncMethodsWithCombinators1()
{
Task<string> t1 = GreetingAsync("Stephanie");
Task<string> t2 = GreetingAsync("Matthias");
await Task.WhenAll(t1, t2);
Console.WriteLine("运行 所有方法.\n 结果 1: {0}\n 结果 2: {1}", t1.Result, t2.Result);
}

  3.6)转换异步模式

使用Task类.Factory.FromAsync方法可以将同步调用转换为异步调用

第一个参数为开始调用BeginInvoke,第二个参数为结束调用EndInvoke,第三个参数为开始调用的委托参数值

private static async void ConvertingAsyncPattern()
{
string s = await Task<string>.Factory.FromAsync(BeginGreeting, EndGreeting, "Angela", null);
Console.Write(s); }

4)错误处理

使用Task.Delay(1000)设置任务延迟时间

  4.1)异步方法的异常处理

异步方法的异常,使用await关键字进行等待,并且包含在try-catch代码块中

  4.2)多个异步方法的异常处理

多个异步方法的异常,使用组合器,处理异常

private async static void StartTwoTasksParallel()
{
Task t1 = null;
try
{
t1 = ThrowAfter(, "first");
Task t2 = ThrowAfter(, "second");
await Task.WhenAll(t1, t2);
}
catch (Exception ex)
{
// just display the exception information of the first task that is awaited within WhenAll
Console.WriteLine("handled {0}", ex.Message);
}
}

  4.3)使用AggregateException信息

通过定义的任务结果,遍历结果的Exception.InnerExceptions获取每一个任务的异常信息

private static async void ShowAggregatedException()
{
Task taskResult = null;
try
{
Task t1 = ThrowAfter(, "first");
Task t2 = ThrowAfter(, "second");
await (taskResult = Task.WhenAll(t1, t2));
}
catch (Exception ex)
{
// just display the exception information of the first task that is awaited within WhenAll
Console.WriteLine("handled {0}", ex.Message);
foreach (var ex1 in taskResult.Exception.InnerExceptions)
{
Console.WriteLine("inner exception {0} from task {1}", ex1.Message, ex1.Source);
}
}
}

5)取消

  5.1)开始取消任务

在System.Threading命名空间中定义了CancellationTokenSource类用于取消发送请求,使用Cancel方法进行取消或者使用CancelAfter设置指定时间后取消

  5.2)使用框架特性取消任务

通过CancellationTokenSource类对象cts.Token属性可以判断任务是否取消,通过指定Token属性进行取消任务

  5.3)取消自定义任务

通过CancellationTokenSource类对象cts.Token.ThrowIfCancellationRequested();

await Task.Run(()=>
{ var images=req.Parse(resp);
foreach(var image in images)
{
cts.Token.ThrowIfCancellatioRequested();
searchInfo.List.Add(image);
}
},cts.Token);

异步的三种模式:

1. 等待模式,在发起了异步方法以及做了一些其它处理之后,原始线程就中断,并且等待异步方法完成之后再继续。

2. 轮询模式,原始线程定期检查发起的线程是否完成,如果没有则可以继续做一些其它的事情。

3. 回调模式,原始线程一直在执行,无需等待或检查发起的线程是否完成。在发起的线程中的引用方法完成之后,发起的线程就会调用回调方法,由回调方法在调用EndInvoke之前处理异步方法的结构。

6)学习检验

  题目:

  1)  为什么要使用异步

  2)    异步解决了软件应用中的哪些问题

  3)  Async和await分别是什么

  4)  什么是并行编程

  5)  委托包含的2种异步方法是什么

  6)  基于事件的异步和APM异步调用的区别

  7)  同步与异步的区别

  8)  组合器是什么

  9)  CancellationTokenSource类是用来做什么的

  10)  Task类任务中使用什么方法可以延续任务调用

C#高级编程9-第13章 异步编程的更多相关文章

  1. 《C#并行编程高级教程》第9章 异步编程模型 笔记

    这个章节我个人感觉意义不大,使用现有的APM(异步编程模型)和EAP(基于时间的异步模型)就很够用了,针对WPF和WinForm其实还有一些专门用于UI更新的类. 但是出于完整性,还是将一下怎么使用. ...

  2. 《深入浅出Node.js》第4章 异步编程

    @by Ruth92(转载请注明出处) 第4章 异步编程 Node 能够迅速成功并流行起来的原因: V8 和 异步 I/O 在性能上带来的提升: 前后端 JavaScript 编程风格一致 一.函数式 ...

  3. 温故知新,CSharp遇见异步编程(Async/Await),聊聊异步编程最佳做法

    什么是异步编程(Async/Await) Async/Await本质上是通过编译器实现的语法糖,它让我们能够轻松的写出简洁.易懂.易维护的异步代码. Async/Await是C# 5引入的关键字,用以 ...

  4. C#编程总结(六)异步编程

    C#编程总结(六)异步编程 1.什么是异步? 异步操作通常用于执行完成时间可能较长的任务,如打开大文件.连接远程计算机或查询数据库.异步操作在主应用程序线程以外的线程中执行.应用程序调用方法异步执行某 ...

  5. 重新想象 Windows 8 Store Apps (44) - 多线程之异步编程: 经典和最新的异步编程模型, IAsyncInfo 与 Task 相互转换

    [源码下载] 重新想象 Windows 8 Store Apps (44) - 多线程之异步编程: 经典和最新的异步编程模型, IAsyncInfo 与 Task 相互转换 作者:webabcd 介绍 ...

  6. 【读书笔记】C#高级编程 第十三章 异步编程

    (一)异步编程的重要性 使用异步编程,方法调用是在后台运行(通常在线程或任务的帮助下),并不会阻塞调用线程.有3中不同的异步编程模式:异步模式.基于事件的异步模式和新增加的基于任务的异步模式(TAP, ...

  7. Windows核心编程:第13章 内存体系结构

    Github https://github.com/gongluck/Windows-Core-Program.git //第13章 内存体系结构.cpp: 定义应用程序的入口点. // #inclu ...

  8. 【读书笔记】《深入浅出nodejs》第四章 异步编程

    1. 异步编程的基础 -- 函数式编程 (1)高阶函数 -- 是可以把函数作为参数,或是将函数作为返回值的函数. (2)偏函数用法 -- 创建一个调用另外一个部分 -- 参数或变量已经预置的函数 -- ...

  9. Java编程的逻辑 (94) - 组合式异步编程

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

随机推荐

  1. centos6.5环境下svn服务器和客户端配置实用详解

    一.服务器端配置 安装 # yum install -y subversion yum安装软件,不清除软件包的方法 # vim /etc/yum.conf keepcache=0 建立svn版本库数据 ...

  2. mysql数据库基于LVM快照的备份

    lvm-snapshot: 基于LVM快照的备份 1.事务日志跟数据文件必须在同一个卷上          2.创建快照卷之前,要请求mysql的全局锁,在快照创建完成之后释放锁          3 ...

  3. Go语言学习之路(持续更新中)

    菜鸟 Go语言教程 教程(RUNOOB.COM):http://www.runoob.com/go/go-tutorial.html Go全球官网:https://golang.org/ (2018- ...

  4. 怎样使用github

    https://www.zhihu.com/question/20070065 https://www.shiyanlou.com/courses/868/labs/3163/document 初次尝 ...

  5. PHP数组序列化和反序列化

    PHP序列化在我们实际项目运行过程中是一种非常常见的操作.比如当我们想要将数组值存储到数据库时,就可以对数组进行序列化操作,然后将序列化后的值存储到数据库中.其实PHP序列化数组就是将复杂的数组数据类 ...

  6. Ubuntu 下安装LEMP环境 实战

    ---恢复内容开始--- 1.nginx的服务端的安装 打开命令行终端,在终端输入,sudo apt-get install nginx  回车即开始安装 kxlc-t@ubuntu:~$ sudo ...

  7. docker安装sonarqube及实际应用

    由于平台的多样化,在不同环境的安装方式可能也不一样,为了避免环境不一致带来的差异,特记一笔容器安装: 一.Sonar可以从以下七个维度检测代码质量,而作为开发人员至少需要处理前5种代码质量问题. 1. ...

  8. JavaScriptDom操作与高级应用(八)

    一:Dom操作基础与高级应用 Node接口也定义了一些所有节点类型都包含的属性和方法.

  9. DbCommandInterceptor抓取EF执行时的SQL语句

    EF6.1也出来不少日子了,6.1相比6.0有个很大的特点就是新增了System.Data.Entity.Infrastructure.Interception 命名空间,此命名空间下的对象可以允许我 ...

  10. day8--socket网络编程进阶

    socket:socket就是实现服务器和客户端数据的交换,服务器端接收并发送数据,客户端发送并接收数据,并且需要注意的是,在python3中,socket值接收字节.因为客户端在发送连接给服务器的时 ...