C#高级编程9-第13章 异步编程
异步编程
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章 异步编程的更多相关文章
- 《C#并行编程高级教程》第9章 异步编程模型 笔记
这个章节我个人感觉意义不大,使用现有的APM(异步编程模型)和EAP(基于时间的异步模型)就很够用了,针对WPF和WinForm其实还有一些专门用于UI更新的类. 但是出于完整性,还是将一下怎么使用. ...
- 《深入浅出Node.js》第4章 异步编程
@by Ruth92(转载请注明出处) 第4章 异步编程 Node 能够迅速成功并流行起来的原因: V8 和 异步 I/O 在性能上带来的提升: 前后端 JavaScript 编程风格一致 一.函数式 ...
- 温故知新,CSharp遇见异步编程(Async/Await),聊聊异步编程最佳做法
什么是异步编程(Async/Await) Async/Await本质上是通过编译器实现的语法糖,它让我们能够轻松的写出简洁.易懂.易维护的异步代码. Async/Await是C# 5引入的关键字,用以 ...
- C#编程总结(六)异步编程
C#编程总结(六)异步编程 1.什么是异步? 异步操作通常用于执行完成时间可能较长的任务,如打开大文件.连接远程计算机或查询数据库.异步操作在主应用程序线程以外的线程中执行.应用程序调用方法异步执行某 ...
- 重新想象 Windows 8 Store Apps (44) - 多线程之异步编程: 经典和最新的异步编程模型, IAsyncInfo 与 Task 相互转换
[源码下载] 重新想象 Windows 8 Store Apps (44) - 多线程之异步编程: 经典和最新的异步编程模型, IAsyncInfo 与 Task 相互转换 作者:webabcd 介绍 ...
- 【读书笔记】C#高级编程 第十三章 异步编程
(一)异步编程的重要性 使用异步编程,方法调用是在后台运行(通常在线程或任务的帮助下),并不会阻塞调用线程.有3中不同的异步编程模式:异步模式.基于事件的异步模式和新增加的基于任务的异步模式(TAP, ...
- Windows核心编程:第13章 内存体系结构
Github https://github.com/gongluck/Windows-Core-Program.git //第13章 内存体系结构.cpp: 定义应用程序的入口点. // #inclu ...
- 【读书笔记】《深入浅出nodejs》第四章 异步编程
1. 异步编程的基础 -- 函数式编程 (1)高阶函数 -- 是可以把函数作为参数,或是将函数作为返回值的函数. (2)偏函数用法 -- 创建一个调用另外一个部分 -- 参数或变量已经预置的函数 -- ...
- Java编程的逻辑 (94) - 组合式异步编程
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...
随机推荐
- mybatis二级缓存应用及与ehcache整合
mybaits的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存. 1.开启mybatis的二级缓存 在核心配 ...
- centos常用网络管理命令
网卡配置命令:ifconfig (ip addr , ip link) ifconfig:显示所有活动状态的相关信息 ifconfig Interface:仅显示指定接口的相关信息 ifc ...
- Android网络通信(7):NFC
Android网络通信之 NFC NFC:近场通信,是一种超近距离的无线通信技术.Android从2.3版本的SDK开始支持基于NFC通信.基于NFC的识别和通信可分为三个步骤:1.Android通过 ...
- 通过使用CSS字体阴影效果解决hover图片时显示文字看不清的问题
1.前言 最近需要加入一个小功能,在鼠标越过图片时,提示其大小和分辨率,而不想用增加属性title来提醒,不够好看.然而发现如果文字是一种颜色,然后总有概率碰到那张图上浮一层的文字会看不到,所以加入文 ...
- 如何设置font-family
第一部分: 根据font-family的原则,假如客户终端不认识前面的字体,就自动切换到第二种字体,第二种不认识就切换到第三种,以此类推.假如都不能识别就调用默认字体 根据font-family的字体 ...
- pytest五:fixture_autouse=True
平常写自动化用例会写一些前置的 fixture 操作,用例需要用到就直接传该函数的参数名称就行了.当用例很多的时候,每次都传返个参数,会比较麻烦.fixture 里面有个参数 autouse,默讣是 ...
- Linux下配置自动更新时间
1,修正本地时区及ntp服务 [root@VM_0_13_centos ~]# yum -y install ntp [root@VM_0_13_centos ~]# rm -rf /etc/loca ...
- python 全栈开发,Day142(flask标准目录结构, flask使用SQLAlchemy,flask离线脚本,flask多app应用,flask-script,flask-migrate,pipreqs)
昨日内容回顾 1. 简述flask上下文管理 - threading.local - 偏函数 - 栈 2. 原生SQL和ORM有什么优缺点? 开发效率: ORM > 原生SQL 执行效率: 原生 ...
- .net的架构模式
一:ADO.NET实现三层架构 不用三层的普通的查询写法: string sql = string.Format("select * from Studnet where StuName l ...
- 解决在Pycharm中无法显示代码提示的问题
#coding: utf-8from cx_Oracle.CURSOR import *import cx_Oracle conn= cx_Oracle.connect('XX', 'XX', '12 ...