异步编程的基础知识

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. webpack常见的配置项

    使用vue init webpack test(项目文件夹名)命令初始化一个vue项目,cd test,然后安装依赖npm install之后会生成一些默认的文件夹和文件,这些文件和文件夹中有些和配置 ...

  2. python while and for

    一.while循环 1.格式: while 条件: while循环体 else: 循环正常跳出执行的语句 2.实例: index= : : break #直接跳出while ,不会执行else els ...

  3. HTTP Health Checks

    This article describes how to configure and use HTTP health checks in NGINX Plus and open source NGI ...

  4. 如何给30台centos7服务器分别增加相同的用户

    老大直接给了30台新鲜的生产服务器,要给每一台服务器增加一个用户,密码相同 难道我们要部署一个工具吗?这样对生产环境可能会产生影响,为了保证服务器的新鲜以及节约时间,研究了小半天,终于研究出一个不是很 ...

  5. UVA215-Spreadsheet Calculator(模拟+拓扑排序)

    Problem UVA215-Spreadsheet Calculator Accept:401  Submit:2013 Time Limit: 3000 mSec Problem Descript ...

  6. P2370 yyy2015c01的U盘(二分+背包)

    思路:先说一下题意吧.就是给你n个文件大小为v,价值为c, 但是硬盘的大小为S, 而且要存的总价值大于等于p.问每次传输k大小的文件.问k的最大值是多少? 我们以k为二分对象. 直接讲检验函数吧. 假 ...

  7. 学习! ! ! Study! ! !

    我们是年轻人,钱不重要,前途才重要,干嘛着急挣钱啊.  学习!!!  study!!!

  8. 认识与防御XSS攻击

    什么是xss攻击? XSS,即(Cross Site Scripting)中文名称为“跨站脚本攻击”.XSS的重点不在于跨站攻击而在于脚本攻击.攻击者可以利用 web应用的漏洞或缺陷之处,向页面注入恶 ...

  9. 编程从入门到放弃(Java)

      1.Java入门篇 1.1 基础入门和面向对象 1.1.1 编程基础 [01] Java语言的基本认识 [02] 类和对象 [03] 类的结构和创建对象 [04] 包和访问权限修饰符 [05] 利 ...

  10. ODPS-Java-SDK快速入门

    一.简介 核心接口包括:AliyunAccount,MaxCompute(SDK中使用原名ODPS)等常见对象组件 更多参见文档:https://help.aliyun.com/document_de ...