(一)异步编程的重要性

使用异步编程,方法调用是在后台运行(通常在线程或任务的帮助下),并不会阻塞调用线程。有3中不同的异步编程模式:异步模式、基于事件的异步模式和新增加的基于任务的异步模式(TAP,可利用async和await关键字来实现)。

(二)异步模式

1、C#1的APM 异步编程模型(Asynchronous Programming Model)。

2、C#2的EAP 基于事件的异步模式(Event-based Asynchronous Pattern)。

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

参考:http://www.cnblogs.com/zhaopei/p/async_one.html

(三)异步编程基础

async和await关键字只是编译器功能。编译器会用Task类创建代码。

1、创建任务

 1 /// <summary>
2 /// 同步方法
3 /// </summary>
4 /// <returns></returns>
5 static string SayHi(string name)
6 {
7 Thread.Sleep(3000);
8 return "你好!"+ name;
9 }
10
11 /// <summary>
12 /// 基于任务的异步模式
13 /// </summary>
14 /// <returns></returns>
15 static Task<string> SayHiAsync(string name)
16 {
17 return Task.Run<string>(()=> {
18 return SayHi(name);
19 });
20 }

泛型版本的Task.Run<string>创建一个返回字符串的任务。

2、调用异步方法

使用await关键字需要有用async修饰符声明的方法。在await的方法没有完成前,该方法内的其他代码不会继续执行,但是调用await所在方法的线程不会被阻塞。

private async static void CallerWithAsync()
{
string result = await SayHiAsync("张三");
Console.WriteLine(result);
}

async修饰符只能用于返回Task和void方法。

3、延续任务

Task类的ContinueWith方法定义了任务完成后就调用的代码。指派给ContinueWith方法的委托接受将已完成的任务作为参数传入,使用Result属性可以访问任务返回的结果。

private static void CallerWithContinuationTask()
{
Task<string> t = SayHiAsync("李四");
t.ContinueWith(_t => Console.WriteLine(_t.Result));
}

4、同步上下文

WPF应用程序设置了DispatcherSynchronizationContext属性,WindowsForm应用程序设置了WindowsFormsSynchronizationContext属性。如果调用异步方法的线程分配给了同步上下文,await完成之后将继续执行。如果不使用相同的上下文,必须调用Task类的ConfigureAwait(ContinueOnCapturedContext:false)。

5、使用多个异步方法

(1)按顺序调用异步方法

private async static void MultipleCallerWithAsync()
{
string result1 = await SayHiAsync("张三");
string result2 = await SayHiAsync("李四");
Console.WriteLine("完成了两次打招呼!{0} 和 {1}", result1, result2);
}

(2)使用组合器

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

private async static void MultipleCallerWithAsyncWithCombinators1()
{
Task<string> task1 = SayHiAsync("张三");
Task<string> task2 = SayHiAsync("李四");
await Task.WhenAll(task1,task2);
Console.WriteLine("完成了两次打招呼!{0} 和 {1}", result1, result2);
}

Task类定义了WhenAll(全部任务完成才返回)和WhenAny(任意任务完成即返回)两个组合器。

当任务返回类型相同时,可以用数组接受返回结果。

private async static void MultipleCallerWithAsyncWithCombinators2()
{
Task<string> task1 = SayHiAsync("张三");
Task<string> task2 = SayHiAsync("李四");
string [] results=await Task.WhenAll(task1,task2);
Console.WriteLine("完成了两次打招呼!{0} 和 {1}", results[0], results[1]);
}

6、转换异步模式

当某些类没有提供基于任务的异步模式时(仅有BeginXX,EndXX),可以使用TaskFactory类定义的FromAsync方法转换为基于任务的异步模式的方法。

private static async void ConvertingAsyncPattern()
{
Func<string, string> method = SayHi;
string result = await Task<string>.Factory.FromAsync<string>((name, callback,state) => {
return method.BeginInvoke(name, callback, state);
},ar=> {
return method.EndInvoke(ar);
},"王麻子",null);
Console.WriteLine(result);
}

(四)错误处理

1、异步方法的异常处理

异步方法异常的一个较好的处理方式,就是使用await关键字,将其放在try/catch语句中。

 1 static async Task ThrowAfter(int ms, string message)
2 {
3 await Task.Delay(ms);
4 throw new Exception(message);
5 }
6
7 private static async void HandleOneError()
8 {
9 try
10 {
11 await ThrowAfter(2000, "first");
12 }
13 catch (Exception ex)
14 {
15 Console.WriteLine("handled {0}", ex.Message);
16 }
17 }

2、多个异步方法的异常处理

在顺序调用两个及以上会抛出异常的方法时,不可再使用以上方法,因为当第一个异步方法抛出异常时try块里的余下方法不会再被调用。

如果需要将剩余的方法继续执行完,再对异常进行处理,可以使用Task.WhenAll方法,这样不管任务是否抛出异常,都会等到所有任务执行完。但是,也只能看见传递给WhenAll方法的第一个异常。

private static async void HandleOneError()
{
try
{
Task task1 = ThrowAfter(1000, "first");
Task task2 = ThrowAfter(2000, "second");
await Task.WhenAll(task1, task2);
}
catch (Exception ex)
{
Console.WriteLine("handled {0}", ex.Message);
}
}

如果需要将剩余的方法执行完,且获取所有抛出异常,可以在try块外声明任务变量,使其可以在catch块内被访问。这样可以使用IsFaulted属性检查任务的状态,当为true时,可以使用Task类的Exception.InnerException访问异常信息。

private static async void HandleOneError()
{
Task task1 = null;
Task task2 = null;
try
{
task1 = ThrowAfter(1000, "first");
task2 = ThrowAfter(2000, "second");
await Task.WhenAll(task1, task2);
}
catch (Exception ex)
{
if (task1 != null && task1.IsFaulted)
{
Console.WriteLine(task1.Exception.InnerException);
}
if (task2 != null && task2.IsFaulted)
{
Console.WriteLine(task2.Exception.InnerException);
}
}
}

3、使用AggregateException信息

为了得到所有的异常信息,还可以将Task.WhenAll返回的结果写入一个Task变量中,然后访问Task类的Exception属性(AggregateException类型)。AggregateException定义了InnerExceptions属性,它包含了所有的异常信息。

private static async void HandleOneError()
{
Task task = null;
try
{
Task task1 = ThrowAfter(1000, "first");
Task task2 = ThrowAfter(2000, "second");
await (task = Task.WhenAll(task1, task2));
}
catch (Exception)
{
foreach (var exception in task.Exception.InnerExceptions)
{
Console.WriteLine(exception);
}
}
}

(五)取消

1、取消任务

private CancellationTokenSource cts;
private void OnCancel()
{
if (cts != null)
{
cts.Cancel();
//cts.CancelAfter(1000);//等待1000ms后取消
}
}

2、使用框架特性取消任务

private async void OnTaskBasedAsyncPattern()
{
List<string> urlList = new List<string>();
urlList.Add("http://www.baidu.com");
cts = new CancellationTokenSource();
try
{
foreach (var url in urlList)
{
Random rd = new Random();
int i = rd.Next(1, 100); //1到100之间的数,
if (i%2==0)
{
OnCancel();//当随机数为偶数时取消任务
}
var client = new HttpClient();
var response = await client.GetAsync(url, cts.Token);//GetAsync方法会检查是否应该取消操作
var result =await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
}
}
catch (OperationCanceledException ex)//当任务取消时会抛出该异常
{
Console.WriteLine(ex.Message);
}
}

3、取消自定义任务

Task类的Run方法提供了传递CancellationToken参数的重载版本。使用IsCancellationRequest属性检查令牌,用ThrowIfCancellationRequested方法触发异常。

public async void CustomerTask()
{
cts = new CancellationTokenSource();
var list = new List<string>();
list.Add("1");
list.Add("2");
list.Add("3");
var deal_list = new List<int>();
try
{
await Task.Run(() => {
foreach (var item in list)
{
Random rd = new Random();
int i = rd.Next(1, 100); //1到100之间的数,
if (i % 2 == 0)
{
OnCancel();//当随机数为偶数时取消任务
}
if (cts.Token.IsCancellationRequested)
{
Console.WriteLine("处理任务异常,回滚");
deal_list.Clear();
cts.Token.ThrowIfCancellationRequested();
}
deal_list.Add(Convert.ToInt32(item));
Console.WriteLine(item);
}
}, cts.Token);
}
catch (OperationCanceledException ex)
{ Console.WriteLine(ex.Message);
} }

【读书笔记】C#高级编程 第十三章 异步编程的更多相关文章

  1. 读书笔记 - js高级程序设计 - 第十一章 DOM扩展

      对DOM的两个主要的扩展 Selectors API HTML5  Element Traversal 元素遍历规范 querySelector var body = document.query ...

  2. 读书笔记 - js高级程序设计 - 第七章 函数表达式

      闭包 有权访问另一个函数作用域中的变量的函数 匿名函数 函数没有名字 少用闭包 由于闭包会携带包含它的函数的作用域,因此会比其它函数占用更多的内存.过度使用闭包可能会导致内存占用过多,我们建议读者 ...

  3. 读书笔记 - js高级程序设计 - 第六章 面向对象的程序设计

      EcmaScript有两种属性 数据属性 和 访问器属性 数据属性有4个特性 Configurable Enumerable Writable Value   前三个值的默认值都为false   ...

  4. 读书笔记 - js高级程序设计 - 第五章 引用类型

      引用类型 和 类 不是一个概念 用typeof来检测属性是否存在 typeof args.name == "string"  需要实验 访问属性的方法 .号和[] 一般情况下要 ...

  5. 读书笔记 - js高级程序设计 - 第四章 变量 作用域 和 内存问题

      5种基本数据类型 可以直接对值操作 判断引用类型 var result = instanceof Array 执行环境 每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这 ...

  6. 读书笔记 - js高级程序设计 - 第三章 基本概念

    启用严格模式 "use strict" 这是一个 pragma 编译指示 让编码意图更清晰  是一个重要原则 5种简单数据类型 Undefined Null Boolean Num ...

  7. 读书笔记 - js高级程序设计 - 第十五章 使用Canvas绘图

    读书笔记 - js高级程序设计 - 第十三章 事件   canvas 具备绘图能力的2D上下文 及文本API 很多浏览器对WebGL的3D上下文支持还不够好   有时候即使浏览器支持,操作系统如果缺缺 ...

  8. 《C#从现象到本质》读书笔记(九)第11章C#的数据结构

    <C#从现象到本质>读书笔记(九)第11章C#的数据结构 C#中的数据结构可以分为两类:非泛型数据结构和泛型数据结构. 通常迭代器接口需要实现的方法有:1)hasNext,是否还有下一个元 ...

  9. 《C#从现象到本质》读书笔记(八)第10章反射

    <C#从现象到本质>读书笔记(八)第10章反射 个人感觉,反射其实就是为了能够在程序运行期间动态的加载一个外部的DLL集合,然后通过某种办法找到这个DLL集合中的某个空间下的某个类的某个成 ...

随机推荐

  1. 开发人员要学的Docker从入门到日常命令使用(通俗易懂),专业运维人员请勿点!

    一.介绍Docker  1.引言 问题1:开发人员告诉测试说自己的项目已经做好了,给你一个发布包,你去测试吧. ## 测试人员,为什么我运行会报错? ## 开发人员说,我本地运行没有问题呀!   解答 ...

  2. 利用MySQL中的乐观锁和悲观锁实现分布式锁

    背景 对于一些并发量不是很高的场景,使用MySQL的乐观锁实现会比较精简且巧妙. 下面就一个小例子,针对不加锁.乐观锁以及悲观锁这三种方式来实现. 主要是一个用户表,它有一个年龄的字段,然后并发地对其 ...

  3. halcon简易标定代码

    read_image (Image, 'C:/Users/HJ/Desktop/demo1/4.bmp') threshold(Image, Region, 110, 255) closing_cir ...

  4. Oracle 用户密码中包括了“@”字符串的错误提示解决方法

    Oracle 用户密码设置了带有"@"符号,正常登陆总是无法登陆,提示无法解析的连接字符串错误 解决办法:1:修改密码:修改密码使密码中不包括@符号:2:增加转义即可,在密码前后增 ...

  5. CRM汇客 牛刀小试 5个BUG修复

    1.权限管理-用户管理-高级搜索-手机号搜索不可用 1.1现象 1.2解决思路 1.2.1 定位接口 接口名:system/user/list 请求方式:GET请求 1.2.3 确定bug所在位置 b ...

  6. Note -「0/1 Fractional Programming」

    What is that? Let us pay attention to a common problem that we often meet in daily life: There are \ ...

  7. Android多版本flavor配置之资源文件和清单文件合并介绍

    知识背景 Android studio升级到3.0之后,gradle增加了多维度管理配置,便于同一个项目中创建应用的不同版本,分别管理依赖项并签署配置.创建产品风味与创建构建类型类似:只需将它们添加到 ...

  8. 出现 Expected 0 arguments but found 1 的bug原因

    问题:在给FileInputStream传入参数时报错 原以为是导错了包,结果试了几次都不行,最后才发现是项目名和这个方法名重复了,修改项目名就可以了! 红线出只是异常,抛出即可解决

  9. shell脚本常用方法总结

    shell脚本给字符串添加颜色 字颜色: echo -e "\033[30m 黑色字 \033[0m" echo -e "\033[31m 红色字 \033[0m&quo ...

  10. 有一种密码学专用语言叫做ASN.1

    目录 简介 ASN.1的例子 ASN.1中的内置类型 ASN.1中的限制语法 总结 简介 ASN.1是一种跨平台的数据序列化的接口描述语言.可能很多人没有听说过ASN.1, 但是相信有过跨平台编程经验 ...