导航
15 Asynchronous Programming
15.1 异步编程的重要性
.NET Framework4.5新增了并行库(Task Parallel Library,TPL),使得.NET进行并行编程更加的简单。C# 5.0新增了两个关键字:async和await,使得异步编程也不再是难事。本章主要关注async和await的使用。
在异步编程中,一个方法调用是在后台运行的(往往是通过某个任务或者线程的帮助),而调用方所在的线程不会被挂起或者阻塞(not blocked)。
通过学习本章,你可以了解到不同模式的差异,如异步模式(asynchronous pattern),基于事件的异步模式(event-based asynchronous pattern),基于任务的异步模式(task-based asynchronous pattern,TAP)。在TAP中用到了async和await。当你比较这些不同的模式时,你就会发现异步编程真正的优势。
讨论完不同的模式之后,你将会通过创建任务和调用异步方法,学习到异步编程的基础组成。你将会了解到那些隐藏在后续任务(continuation tasks)和异步上下文之间的细节。
需要重点强调的是Error的处理;因为在不同的异步任务中,同样的应用场景需要一些不同的错误处理。
本章最后一部分将讨论Universal Windows App在异步编程中的应用场景以及需要注意的内容。
注意:任务和并行编程将在第21章进行介绍。
当你的应用程序动不动就未响应时,用户会很觉得很烦。虽然在使用鼠标的时候,我们已经习惯了它会有一点小小的延迟,因为我们已经用了它许多年了。但是在触摸式UI中,一个App需要对请求马上做出响应。否则,用户会试图来回碰触(redo the action)。
因为在古老版本的.NET Framework中异步编程很难实现,所以它常常达不到预期的目标。在旧版本的Visual Studio中,经常有一个应用程序会阻塞UI线程。在那种情况下,假如你要打开一个拥有数百个项目的解决方案,将花费相当长的时间(take a long coffee break)。VS2017提供了轻量级的解决方案加载特性,只有当项目需要被用到时才会进行加载,而且,选中的项目会被优先加载。从VS2015开始,NuGet包管理器不再作为一个对话框形式实现。新的NuGet包管理器可以在你进行其他操作的时候,异步加载包信息。这些只是Visual Studio里关于异步编程的一些重要的示例。
.NET的很多API都提供了同步和异步版本。因为同步版本的方法更加容易使用,所以它经常会在不合适的地方被调用。通过新的Windows运行时(Windows Runtime,WinRT),如果一个API调用超过40毫秒,只有它的异步版本是有效的。从C# 5.0开始,异步编程开始变得越来越容易,就跟同步的方式一样,所以使用异步API不需要有任何负担。当然异步API中也有不少坑(traps),本章也会一一介绍。
15.2 异步编程的.NET 历史
在开始使用新的async和await关键字之前,最好先了解一下.NET Framework曾经的异步编程模式。早在.NET Framework 1.0的时候,就已经有了异步的特性,并且.NET Framework中的许多基础类都支持不止一种异步编程模式。
这里,我们主要通过下面的三种方式来执行一个同步的网络请求(synchronous networking call):
- 异步模式(asynchronous pattern)
- 基于事件的异步模式(event-based asynchronous pattern)
- 基于任务的异步模式(task-based asynchronous pattern,TAP)
异步模式是最初的异步处理特性,不单只支持一系列的API,还支持基础功能,如delegate类型。
因为使用异步模式更新UI非常的复杂——不管是通过Windows Forms还是WPF,.NET 2.0开始推荐使用基于事件的异步模式。在这个模式下,控制同步上下文的线程会调用一个事件处理器(event handler),所以这个模式下更新UI会很简单。先说一下,这个模式也被成为异步组件模式(asynchronous component pattern)。
通过.NET Framework 4.5,你可以使用另外一种异步编程的方式,TAP。这种模式基于Task类型,通过使用关键字async和await的编译器特性,来支持异步处理。
示例代码HistorySample使用了至少C# 7.1以上的特性以及下面列出的这些命名空间:
- System
- System.IO
- System.Net
- System.Threading.Tasks
15.2.1 同步调用
让我们用WebClient类开始介绍同步调用。这个类提供了很多同步API,比如DownloadString,DownloadFile以及DownloadData。在下面的代码段里,DownloadString发起了一个HTTP请求,并且将响应内容写到一个string对象里。我们将响应内容的一部分输出到控制台上:
private const string url = "http://www.cninnovation.com";
private static void SynchronizedAPI()
{
Console.WriteLine(nameof(SynchronizedAPI));
using (var client = new WebClient())
{
string content = client.DownloadString(url);
Console.WriteLine(content.Substring(0, 100));
}
Console.WriteLine();
}
你会在控制台上看到这样的内容:
SynchronizedAPI
<!-- basing on ~/assan-v2.5/templates/main-template/home-construction.html -->
<!DOCTYPE html>
<ht
DownloadString方法会阻塞(blocks)整个调用线程,直到它获取完返回值。不建议在客户端的UI主线程调用这类方法,因为它会阻塞用户界面(user interface)。这种等待对于用户来说是不友好(unpleasant)的,因为在网络请求的过程中整个程序完全未响应(unresponsive)了。