异步编程的基础知识

C#5推出的async和await关键字使异步编程从表面上来说变得简单了许多,我们只需要了解不多的知识就可以编写出有效的异步代码。

在介绍async和await之前,先介绍一些基础的概念:

并发:同时做很多事情。

这个解释直接表明了并发的作用。终端用户程序利用并发功能,在输入数据库的同时响应用户输入。服务器应用利用并发,在处理第一个请求的同时响应第二个请求。只要你希望程序同时做多件事情,你就需要并发。几乎每个软件程序 都会受益于并发。

多线程:并发的一种形式,它采用多个线程来执行程序。

从字面上看,多线程就是使用多个线程。本书后续章节将介绍,多线程是并发的一种形式,但不是唯一的形式。实际上,直接使用底层线程类型在现代程序中基本不起作用。 比起老式的多线程机制,采用高级的抽象机制会让程序功能更加强大、效率更高。因此,本书将尽量不涉及一些过时的技术。书中所有多线程的方法都采用高级类型,而不是Thread或BackgroundWorker。但是,不要认为多线程已经彻底被淘汰了!因为线程池要求多线程继续存在。线程池存放任务的队列,这个队列能够根据需要自行调整。相应地,线程池产生了另一个重要的并发形式:并行处理。

并行处理:把正在执行的大量的任务分割成小块,分配给多个同时运行的线程。

为了让处理器的利用效率最大化,并行处理(或并行编程)采用多线程。当现代多核CPU执行大量任务时,若只用一个核执行所有任务,而其他核保持空闲,这显然是不合理的。并行处理把任务分割成小块并分配给多个线程,让它们在不同的核上独立运行。并行处理是多线程的一种,而多线程是并发的一种。在现代程序中,还有一种非常重要但很多人还不熟悉的并发类型:异步编程。

异步编程:并发的一种形式,它采用future模式或回调(callback)机制,以避免产生不必要的线程。

一个future(或promise)类型代表一些即将完成的操作。在.NET中,新版future类型有Task和Task<TResult>。在老式异步编程API中,采用回调或事件(event),而不是future。异步编程的核心理念是异步操作:启动了的操作将会在一段时间后完成。这个操作正在执行时,不会阻塞原来的线程。启动了这个操作的线程,可以继续执行其他任务。当操作完成时,会通知它的future,或者调用回调函数,以便让程序知道操作已经结束。异步编程是一种功能强大的并发形式,但直至不久前,实现异步编程仍需要特别复杂的代码。VS2012支持async和await,这让异步编程变得几乎和同步(非并发)编程一样容易。

异步编程简介

异步编程的执行流程

现代的异步.NET程序使用两个关键字:async和await。async关键字加在方法声明上,它的主要目的是使方法内的await关键字生效(为了保持向后兼容,同时引入了这两个关键字)。如果async方法有返回值,应返回Task<T>;如果没有返回值,应返回Task。这些task类型相当于future,用来在异步方法结束时通知主程序。还有一种是返回void,这种就是存粹的为了兼容事件处理程序。所以,除了用于注册实践处理程序,不建议在别的地方使用返回void的异步代码。

先来看一个例子:

 async Task AsyncMethod()
{
Console.WriteLine("Sync execute before await");//①同步执行的代码
await Task.Delay(TimeSpan.FromSeconds());//②异步等待,非阻塞
Console.WriteLine("callback method");//③任务的延续
}

和其他方法一样,async方法在开始时以同步方式执行①。在async方法内部,await关键字对它的参数执行一个异步等待②。它首先检查操作是否已经完成,如果完成了,就继续运行(同步方式)。否则,它会暂停async方法,并返回,留下一个未完成的task(token)。一段时间后,操作完成,async方法就恢复运行③。就是这么简单。编译器在后面帮助我们做了大量的工作。在后续的章节中,会详细介绍编译器的所作所为。

同步上下文:一个async方法是由多个同步执行的程序块组成的,每个同步程序块之间由await语句分隔②。第一个同步程序块在调用这个方法的线程中运行,但其他同步程序块在哪里运行呢?情况比较复杂。最常见的情况是,用await语句等待一个任务完成,当该方法在await处暂停时,就可以捕捉上下文(context)。如果当前SynchronizationContext不为空,这个上下文就是当前SynchronizationContext。如果当前SynchronizationContext为空,则这个上下文为当前TaskScheduler。该方法会在这个上下文中继续运行③。一般来说,运行UI线程时采用UI上下文,处理ASP.NET请求时采用ASP.NET请求上下文,其他很多情况下则采用线程池上下文。因此,在上面的代码中,每个同步程序块会试图在原始的上下文中恢复运行。如果在UI线程中调用DoSomethingAsync,这个方法的每个同步程序块都将在此UI线程上运行。但是,如果在线程池线程中调用,每个同步程序块将在线程池线程上运行。要避免这种行为,可以在await中使用ConfigureAwait方法,将参数continueOnCapturedContext设为false。接下来的代码刚开始会在调用的线程里运行,在被await暂停后,则会在线程池线程里继续运行。

可等待模式:关键字await不仅能用于Task,还能用于所有遵循特定模式的awaitable类型——在编译器生成的状态机中,有一个MoveNext的方法,在这个方法中会调用await的对象是否有一个GetAwaiter的方法,这个方法是否会返回一个awaiter,awaiter里面是否包含了GetResult方法和IsCompleted属性,awaiter遵循的接口是INotifyCompletion和ICriticalNotifyCompletion,从名字上面来看就知道他们的意思都是发送一个通知。类似的awaitable类型还有YieldAwaitable、ConfiguredTaskAwaitable,awaitable类型的一个特点是有一个GetAwaiter的方法来返回一个awaiter。

迭代器也是类似的原理,并且它出现的更早(C#2)。在foreach循环一个序列的时候,他不要求这个序列必须实现了IEnumerable或者IEnumerable<T>,foreach会寻找这个序列是否有一个GetEnumerator的方法,这个方法是否返回一个Enumerator,这个Enumerator是否包含一个MoveNext方法和一个返回当前元素的Current属性。

处理异常

因为不确定Task的执行线程,所以Task不会主动的抛出异常,在返回的Task中的Status属性上会指示Task的执行结果,Status属性是TaskStatus枚举类型,定义如下:

 public enum TaskStatus
{
Created,
WaitingForActivation,
WaitingToRun,
Running,
WaitingForChildrenToComplete,
RanToCompletion,
Canceled,
Faulted,
}

Task上面还有一个Exception属性,类型是一个AggregateException。正常情况下,当Task执行顺利完成时,Exception返回null。但Task执行失败时,Exception属性会返回一个AggregateException类型的异常,这个异常包含了Task执行过程中抛出的所有异常。发生异常时,任务结束,不直接抛出异常。只有在使用一个Task的时候,比如Task.Wait()、Task.WhenAll()、等等。还有,在await一个Task的时候,也会抛出异常,但是会抛出AggregateException中的第一个异常。

死锁

关于异步编程还有一个重要的准则就是,在有UI线程的地方,如果你要使用异步编程,就要异步到底,考虑下面的代码:

 async Task WaitAsync()
{
//这里awati会捕获当前上下文……
await Task.Delay(TimeSpan.FromSeconds()); // ……这里会试图用上面捕获的上下文继续执行
}
void Deadlock()
{
//开始 延迟
Task task = WaitAsync(); //同步程序块,正在等待异步方法完成
task.Wait();
}

上面的代码如果在UI线程或Asp.net 中执行,就会发生死锁,来看看到底发生了什么:这两种上下文每次只能运行一个线程。Deadlock方法调用WaitAsync方法,WaitAsync方法开始调用delay语句。然后,Deadlock方法(同步)等待WaitAsync方法完成,同时阻塞了上下文线程。当delay语句结束时,await试图在已捕获的上下文中继续运行WaitAsync方法,但这个步骤无法成功,因为上下文中已经有了一个阻塞的线程,并且这种上下文只允许同时运行一个线程。这里有两个方法可以避免死锁:在WaitAsync中使用ConfigureAwait(false)(导致await忽略该方法的上下文),或者用await语句调用WaitAsync方法(让Deadlock变成一个异步方法)。

基础的东西就这么多,这个章节里面没有多少例子,不过如果能看懂里面的所有意思,那么编写异步编程也不是什么难事。下面的章节会详细的介绍编译器为async和await所做的所有事情,并会将相关的概念进行进一步的扩展。

C#复习笔记(5)--C#5:简化的异步编程(异步编程的基础知识)的更多相关文章

  1. java学习笔记 --- 网络编程(网络的基础知识)

    1.网络模型: |--OSI(open stystem Interconnection开放式系统互连) |--特点: 是一种异构系统互连的分层结构:提供了控制互连系统交互规则的标准骨架:定义一种抽象结 ...

  2. 《高性能MySQL》读书笔记之 MySQL锁、事务、多版本并发控制的基础知识

    1.2 并发控制 1.2.1 读写锁 在处理并发读或写时,通过实现一个由两种类型的锁组成的锁系统来解决问题.这两种类型的锁通常被称为 共享锁(shared lock) 和 排它锁(exclusive ...

  3. 《Java核心技术·卷Ⅰ:基础知识(原版10》学习笔记 第5章 继承

    <Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 目录 <Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 5.1 类.超类和子类 5.1 ...

  4. Java基础复习笔记系列 九 网络编程

    Java基础复习笔记系列之 网络编程 学习资料参考: 1.http://www.icoolxue.com/ 2. 1.网络编程的基础概念. TCP/IP协议:Socket编程:IP地址. 中国和美国之 ...

  5. Java基础复习笔记系列 八 多线程编程

    Java基础复习笔记系列之 多线程编程 参考地址: http://blog.csdn.net/xuweilinjijis/article/details/8878649 今天的故事,让我们从上面这个图 ...

  6. Java基础复习笔记系列 七 IO操作

    Java基础复习笔记系列之 IO操作 我们说的出入,都是站在程序的角度来说的.FileInputStream是读入数据.?????? 1.流是什么东西? 这章的理解的关键是:形象思维.一个管道插入了一 ...

  7. Java基础复习笔记系列 五 常用类

    Java基础复习笔记系列之 常用类 1.String类介绍. 首先看类所属的包:java.lang.String类. 再看它的构造方法: 2. String s1 = “hello”: String ...

  8. Java基础复习笔记系列 四 数组

    Java基础复习笔记系列之 数组 1.数组初步介绍? Java中的数组是引用类型,不可以直接分配在栈上.不同于C(在Java中,除了基础数据类型外,所有的类型都是引用类型.) Java中的数组在申明时 ...

  9. Java基础复习笔记基本排序算法

    Java基础复习笔记基本排序算法 1. 排序 排序是一个历来都是很多算法家热衷的领域,到现在还有很多数学家兼计算机专家还在研究.而排序是计算机程序开发中常用的一种操作.为何需要排序呢.我们在所有的系统 ...

  10. 机器学习实战(Machine Learning in Action)学习笔记————09.利用PCA简化数据

    机器学习实战(Machine Learning in Action)学习笔记————09.利用PCA简化数据 关键字:PCA.主成分分析.降维作者:米仓山下时间:2018-11-15机器学习实战(Ma ...

随机推荐

  1. Spring容器技术内幕之BeanWrapper类介绍

    引言 org.springframework.beans.BeanWrapper是Spring框架中重要的组件类.BeanWrapper相当于一个代理器,Spring委托BeanWrapperwanc ...

  2. Spring的jdbc模板1

    Spring是EE开发的一站式框架,有EE开发的每一层解决方案.Spring对持久层也提供了解决方案:ORM模块和jdbc模块,ORM模块在整合其他框架的时候使用 Spring提供了很多的模板用于简化 ...

  3. 关于LVS负载均衡tcp长连接分发的解决思路

    虽然应用keepalived搞定了后端服务负载均衡和高可用性问题,但是在具体应用的时候,还是要注意很多问题.很多应用都用tcp或者http的长连接,因为建立tcp连接或者http连接开销比较大,而应用 ...

  4. 2292: Quality of Check Digits 中南多校 暴力枚举

    #include <cstdio> #include <algorithm> #include <cstring> #include <iostream> ...

  5. 雅礼集训【Day6-1】字符串

    雅礼集训[Day6-1]字符串 假设我们有串\(a\),我们设\(a'\)为\(a\)翻转后按为取反过后的串. 我们只考虑前一半的,长为\(m\)的串.如果前半截匹配了\(a\)或者\(a'\),则\ ...

  6. linux学习笔记整理(五)

    第六章 Centos7用户管理本节所讲内容:6.1 用户和组的相关配置文件6.2 管理用户和组6.3实战:进入centos7 紧急模式恢复root密码 用户一般来说是指使用计算机的人,计算机对针使用其 ...

  7. 设计模式C++实现——装饰者模式

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/walkerkalr/article/details/28633123 模式定义:         装 ...

  8. [2] LabelImg图片标注 与 YOLOv3 网络训练 (待补充)

    LabelImg是一个图形图像注释工具. 它是用Python编写的,并使用Qt作为其图形界面. 注释以PASCAL VOC格式保存为XML文件,这是ImageNet使用的格式.Besdies,它也支持 ...

  9. spring boot 注解方式 idea报could not autowire

    File-Project Structure 页面 Facets下删掉 Spring(直接右键Delete) 这个解答是对的.并不会降低安全性!!因为创建项目的时候,都是先创建空项目再创建web mo ...

  10. 如何在关闭ssh连接的情况下,让进程继续运行?

    #screen 1 回车后进入Screen子界面,此时putty标题栏会指示处于子界面状态,然后运行你的程序 #应用程序名 1 然后按下Ctrl+A后抬起,然后按下d键,此时切换回主界面,Putty的 ...