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

(一)异步编程的重要性

使用异步编程,方法调用是在后台运行(通常在线程或任务的帮助下),并不会阻塞调用线程。有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);
    }

}
时间: 2024-11-08 19:03:56

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

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

闭包 有权访问另一个函数作用域中的变量的函数 匿名函数 函数没有名字 少用闭包 由于闭包会携带包含它的函数的作用域,因此会比其它函数占用更多的内存.过度使用闭包可能会导致内存占用过多,我们建议读者只在绝对必要时再考虑使用闭包 模块模式   增强的模块模式   特权方法 有权访问私有变量的公有方法叫做特权方法 块级作用域   实现单例的特权方法  

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

5种基本数据类型 可以直接对值操作 判断引用类型 var result = instanceof Array 执行环境 每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中 执行环境的销毁 某个执行环境中的所有代码执行完毕后 该环境被销毁 保存在其中的所有变量了函数定义也会随之销毁 作用域链中的对象 全局执行环境的变更对象始终都是作用域链中的最后一个对象 没有块级作用域 if 和 for 内的变量 外部也可以访问 标记清除 不同浏览器 只不过垃圾时间的长短不同 引

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

启用严格模式 "use strict" 这是一个 pragma 编译指示 让编码意图更清晰  是一个重要原则 5种简单数据类型 Undefined Null Boolean Number String 1种复杂数据类型 Object 检测数据类型的方法 typeof 有如下值: undefined boolean object string number function typeof Null object 意在保存对象还没有保存对象的变量的初始值最好是什么 null 八进制的第一位

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

EcmaScript有两种属性 数据属性 和 访问器属性 数据属性有4个特性 Configurable Enumerable Writable Value 前三个值的默认值都为false 举例 Object.defineProperty( person, "name", { writable:false, value:"niko"} ) ; 一旦属性定义为不可配置的,就不能再把它变回可配置的了 读取属性 的特性 var descriptor  = Object.ge

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

对DOM的两个主要的扩展 Selectors API HTML5 Element Traversal 元素遍历规范 querySelector var body = document.querySelector("body"); var myDiv = document.querySelector("#myDiv"); 取得id为myDiv的元素 var selected = document.querySelector(".selected")

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

读书笔记 - js高级程序设计 - 第十三章 事件 canvas 具备绘图能力的2D上下文 及文本API 很多浏览器对WebGL的3D上下文支持还不够好 有时候即使浏览器支持,操作系统如果缺缺乏必要的绘图驱动程序,则浏览器即使支持了也没用   <canvas> var drawing = document.getElementById("drawing"); if( drawing.getContext ){ drawing.getContext("2d"

Java学习笔记—第十三章 数据库编程入门

第十三章 数据库编程入门 了解JDBC Java中对数据库的访问主要是通过JDBC进行的.JDBC是Java数据库连接技术(Java Database Connectivity)的简称,是用于执行SQL语句的API,可以为多种关系数据库提供统一访问.由一组用Java语言编写的类和接口组成.(SQL是Structure Query Language的缩写,意义为结构化查询语言,是一种标准的关系数据库访问语言.) JDBC的工作机制 使用JDBC完成对数据库的访问主要包括以下五个层次:Java应用程

javascript高级程序设计 第十三章--事件

javascript高级程序设计 第十三章--事件js与HTML的交互就是通过事件实现的,事件就是文档或浏览器窗口中发生的一些特定的交互瞬间. 事件流:事件流描述的是从页面中接收事件的顺序,IE的是事件冒泡流,Netscape的是事件捕获流,这个两个是完全相反的事件流概念. 事件冒泡:由最具体的元素接收,然后逐级向上传播到更高级的节点,即事件沿DOM树向上传播,直到document对象. 事件捕获:不大具体的节点应该更早接收到事件,相当于沿DOM节点树向下级传播直到事件的实际目标,在浏览器中,是

第十三章 异步和数据驱动编程

第十三章异步和数据驱动编程 本章介绍 ■异步工作流编程 ■使用 F# Interactive 浏览数据 ■使用度量单位定义类型 ■处理与可视化数据 我们首先引述了一次对比尔 · 盖茨的采访,他谈到他感兴趣的编程任务的类型,并描述了编写应用程序的典型情况: 从 web 获取数据,不只是考虑把它当作文本,而且要引入结构,然后- -,尝试不同的数据表现方式,且以交互的方式.- 写很少的代码,可以有自己专门的算法来处理数据.[2008年,盖茨] 这正好说出了我们在这一章所要做的,我们将会看到,F# 语言