C#复习笔记(5)--C#5:简化的异步编程(异步编程的基础知识)
异步编程的基础知识
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:简化的异步编程(异步编程的基础知识)的更多相关文章
- java学习笔记 --- 网络编程(网络的基础知识)
1.网络模型: |--OSI(open stystem Interconnection开放式系统互连) |--特点: 是一种异构系统互连的分层结构:提供了控制互连系统交互规则的标准骨架:定义一种抽象结 ...
- 《高性能MySQL》读书笔记之 MySQL锁、事务、多版本并发控制的基础知识
1.2 并发控制 1.2.1 读写锁 在处理并发读或写时,通过实现一个由两种类型的锁组成的锁系统来解决问题.这两种类型的锁通常被称为 共享锁(shared lock) 和 排它锁(exclusive ...
- 《Java核心技术·卷Ⅰ:基础知识(原版10》学习笔记 第5章 继承
<Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 目录 <Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 5.1 类.超类和子类 5.1 ...
- Java基础复习笔记系列 九 网络编程
Java基础复习笔记系列之 网络编程 学习资料参考: 1.http://www.icoolxue.com/ 2. 1.网络编程的基础概念. TCP/IP协议:Socket编程:IP地址. 中国和美国之 ...
- Java基础复习笔记系列 八 多线程编程
Java基础复习笔记系列之 多线程编程 参考地址: http://blog.csdn.net/xuweilinjijis/article/details/8878649 今天的故事,让我们从上面这个图 ...
- Java基础复习笔记系列 七 IO操作
Java基础复习笔记系列之 IO操作 我们说的出入,都是站在程序的角度来说的.FileInputStream是读入数据.?????? 1.流是什么东西? 这章的理解的关键是:形象思维.一个管道插入了一 ...
- Java基础复习笔记系列 五 常用类
Java基础复习笔记系列之 常用类 1.String类介绍. 首先看类所属的包:java.lang.String类. 再看它的构造方法: 2. String s1 = “hello”: String ...
- Java基础复习笔记系列 四 数组
Java基础复习笔记系列之 数组 1.数组初步介绍? Java中的数组是引用类型,不可以直接分配在栈上.不同于C(在Java中,除了基础数据类型外,所有的类型都是引用类型.) Java中的数组在申明时 ...
- Java基础复习笔记基本排序算法
Java基础复习笔记基本排序算法 1. 排序 排序是一个历来都是很多算法家热衷的领域,到现在还有很多数学家兼计算机专家还在研究.而排序是计算机程序开发中常用的一种操作.为何需要排序呢.我们在所有的系统 ...
- 机器学习实战(Machine Learning in Action)学习笔记————09.利用PCA简化数据
机器学习实战(Machine Learning in Action)学习笔记————09.利用PCA简化数据 关键字:PCA.主成分分析.降维作者:米仓山下时间:2018-11-15机器学习实战(Ma ...
随机推荐
- K-means算法的matlab程序
K-means算法的matlab程序 在“K-means算法的matlab程序(初步)”这篇文章中已经用matlab程序对iris数据库进行简单的实现,下面的程序最终的目的是求准确度. 作者:凯鲁嘎吉 ...
- linux远程目录共享
一.环境介绍 1.服务器说明: 有两台服务器,(1)101报表服务器,上面是tomcat跑的原生FineReport报表系统,(2)103业务服务器,上面是具体的业务系统. 2.需求说明: 报表文件由 ...
- 第一次使用Open Live Writer维护BlogJava
换了电脑,又重装了一堆东西,现在才把Open Live Writer整好.顺便记下几个心得: Open Live Writer已经没办法从网站上下载了,介绍个方法,可以把地址直接拷贝到迅雷里面,然后请 ...
- 1.tushare模块的应用
tushare模块的应用 今日概要 TuShare简介和环境安装 TuShare的应用 今日详情 一.TuShare简介和环境安装 TuShare是一个著名的免费.开源的python财经数据接口包.其 ...
- nginx基本配置与参数说明
user nobody; #启动进程,通常设置成和cpu的数量相等 worker_processes 1; #全局错误日志及PID文件 #error_log logs/error.log; # ...
- 一个特别好用的属性:inline-block
说起inline-block,大家都不陌生,如果我要保证:有一个内联元素,保证它换行时,不被截断,而要整体换行,那么设置:display:inline-block 即可
- 让vue-cli脚手架搭建的项目可以处理vue文件中postcss语法
图中&属于postcss的语法,这样书写样式可以清楚的看出选择器之前的层级关系,非常好用. 在利用vue-cli脚手架搭建的项目中如果不配置是不支持这种写法的,这样写不会报错,但是样式不生效. ...
- 手把手丨我们在UCL找到了一个糖尿病数据集,用机器学习预测糖尿病(三)
梯度提升: from sklearn.ensemble import GradientBoostingClassifier gb=GradientBoostingClassifier(random_s ...
- P2360 地下城主(BFS)
感觉这道题还是蛮简单的,不过使使用了4个队列(其实只是一个)emmmmm,还是很好的 #include<iostream> #include<string> #include& ...
- TCP三次握手与四次握手
背景描述 通过上一篇中网络模型中的IP层的介绍,我们知道网络层,可以实现两个主机之间的通信.但是这并不具体,因为,真正进行通信的实体是在主机中的进程,是一个主机中的一个进程与另外一个主机中的一个进程在 ...