异步编程 async/await 初步
async/await 简介
异步方法的主要应用场景:(1)计算密集型(2) I/O 密集型
异步编程能提升网络服务器的吞吐量,提高用户界面的响应性。
C# 直接在语言层面上支持异步编程,它使用 async/await 语法,在 Task asynchronous programming (TAP) 模型上实现了多线程编程。
使用 async/await 编写异步代码时,形式上与同步代码差不多,无需分心编写通常重复的线程管理代码,因而能更专注于业务逻辑。
由于形式简单,它降低了编写多线程代码的负担,减少出错几率,提高了编码效率。
同步代码与异步代码比较
例如,一个基本串流拷贝操作,看一下它的同步版本与异步版本在代码实现形式上有什么不同:
从代码可以看出,异步版本函数把返回类型void,改成 async Task,在同步方法调用前面加入 await,并换成了异步方法, 这样就可以实现异步功能,是不是很简单?
从代码阅读角度来讲,同步版本和异步版本流程是基本一致的,它符合人的思维习惯,也很容易理解。
在异步版本中,async 用来说明该函数是一个异步函数。如果函数没有返回值,则返回 Task; 如果函数需要返回 TResult,则返回Task<TResult>。 函数里面在需要等待异步代码完成的地方,加上 await 关键字,表示要等待它完成后再继续。 根据需要,可以加入更多的同步代码,加入多个 await 来等待其它异步代码。
C# 编译器会特殊处理异步函数,为它生成依赖 Task 机制的代码,在 DotNet 自身的多线程支持环境下,完成多线程之间的协作,保证代码按照指定的方式正确运行。
异步 IO 例子
我们来看一个访问网络资源的例子:
计算密集型例子
耗时的 CPU 计算任务可以放到 Task.Run() 来执行,避免阻塞当前线程。
下面例子功能为:计算指定数字以内的所有质数的个数。例子里面使用 Stopwatch 类来评估运行所用的时间。
异步操作的异常处理
await 可以捕获到异步操作里抛出的异常。
下面这个例子展示:如果文件不存在,将会显示出错误信息;如果碰到其它类型错误,也会显示出来。
异步操作的中途取消
可以使用一个 CancellationTokenSource 实例来取消多个任务。
CancellationTokenSource 提供了 Token 属性,类型为 CancellationToken,作为参数传给异步任务。
可以调用 CancellationTokenSource 的 Cancel() 方法来设置 CancellationToken 的状态。
异步任务根据 CancellationToken 的状态来控制自己的执行。
下面是一个加入取消功能的计算质数个数的例子:
不取消时的输出类似为:
中途按键盘 “C” 键取消任务时的输出类似为:
异步操作的返回类型
异步操作支持多种返回类型:
- Task,对应方法不需要返回值,即 void
- Task<TResult>,对应方法返回 TResult
- void,对应事件处理函数
- 返回类型有可访问的 GetAwaiter 方法
- IAsyncEnumerable<T>,对应方法返回一个异步流
下面是一个返回 IAsyncEnumerable<T> 的例子,可以用 await foreach 语句来消费异步流。
结束语
C# 的 async/await 语法简化了异步编程模型,它是在 DotNet 的 Task 机制上实现的,在使用过程中,往往也会用到 Task 的各种方法。