简介
- .NET Framework 4.5 将 任务并行库(Task Parallel Library, TPL)添加到.NET中,使并行编程 更加容易
- C# 5.0 增加了 两个关键字 来简化 异步编程:
async和await
使用 异步编程:方法的调用是在后台运行(通常是在线程或者任务的帮助下),并且不会阻塞调用线程
三种 不同模式的 异步编程
第一种:异步模式
- 是处理异步问题的第一种方式
- 从.NET Framework 1.0开始,就提供了的异步功能
- .NET Framework的许多类都实现了一个或多个异步模式
- 定义了
BeginXXX方法和EndXXX方法:BeginXXX方法:- 输入参数:
- 接受其同步方法的所有输入参数
- 还可以接受一个
AsyncCallback委托类型的输入参数- 用于指定,异步方法执行结束后,需要做的事情
- 委托类型
AsyncCallback定义为:入参为IAsyncResult类型, 返回值为void - 具体实现为一个回调方法
- 这个回调方法,不在调用线程中运行,而是在异步方法执行的线程中运行,因此,如果调用线程是UI线程,则在这个回调方法不在UI线程中运行,也就无法访问UI上的元素 (试图访问UI元素的时候,会抛出异常:The calling thread cannot access this object because a different thread owns it)
- 返回类型:
IAsyncResult。用于验证调用是否已经完成,并一直等到方法的执行结束
- 输入参数:
EndXXX方法:- 输入参数:接受
BeginXXX的所有输出参数 - 返回类型:其同步方法的返回类型
- 输入参数:接受
- 例如:同步方法
DownloadString()的异步版本就是BeginDownloadString()和EndDownloadString()
- 委托类型是否支持这种异步模式?
- .NET Framework 支持
- .NET Core 不支持。调用委托的这些方法时,会抛出一个异常。
第二种:基于事件的 异步模式
- 又称 异步组件模式
- 由于第一种方式更新UI非常复杂,所以.NET Framework 2.0推出了这第二种处理异步问题的方式,使得更新UI变得容易
- 事件处理程序被拥有同步上下文的线程调用。在Windows Forms, WPF和UWP中,这就是UI线程。因此可以直接从事件处理程序中访问UI元素。
第三种:基于任务的 异步模式。它是利用
async和await关键字来实现
- .NET Framework 4.5 推出这第三种方式
- 基于
Task类的异步模式(TAP) await关键字会解除调用线程的阻塞,转而去完成其他事情。异步方法在后台执行,当异步方法执行完成时,调用线程就可以从后台Task中获得结果,然后继续执行await后面的代码async创建了一个状态机,类似于yield return语句- 一切都变简单了:
- 没有阻塞, 也不需要切换回调用线程。这些都是自动实现的。
- 代码顺序也和惯用的同步编程一样
Main方法要使用async, 则需要使用C# 7.1及以上
举个栗子
using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
namespace AsyncHistory
{
class Program
{
private const string url = "http://www.cninnovation.com";
static async Task Main()
{
SynchronizedAPI();
AsynchronousPattern();
EventBasedAsyncPattern();
await TaskBasedAsyncPatternAsync();
Console.ReadLine();
}
private static async Task TaskBasedAsyncPatternAsync()
{
Console.WriteLine(nameof(TaskBasedAsyncPatternAsync));
using (var client = new WebClient())
{
string content = await client.DownloadStringTaskAsync(url);
Console.WriteLine(content.Substring(0, 100));
Console.WriteLine();
}
}
private static void EventBasedAsyncPattern()
{
Console.WriteLine(nameof(EventBasedAsyncPattern));
using (var client = new WebClient())
{
client.DownloadStringCompleted += (sender, e) =>
{
Console.WriteLine(e.Result.Substring(0, 100));
};
client.DownloadStringAsync(new Uri(url));
Console.WriteLine();
}
}
private static void AsynchronousPattern()
{
Console.WriteLine(nameof(AsynchronousPattern));
WebRequest request = WebRequest.Create(url);
IAsyncResult result = request.BeginGetResponse(ReadResponse, null);
void ReadResponse(IAsyncResult ar)
{
using (WebResponse response = request.EndGetResponse(ar))
{
Stream stream = response.GetResponseStream();
var reader = new StreamReader(stream);
string content = reader.ReadToEnd();
Console.WriteLine(content.Substring(0, 100));
Console.WriteLine();
}
}
}
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();
}
}
}
其中:同步调用
private static void SynchronizedAPI()
{
using (var client = new WebClient())
{
string content = client.DownloadString(url);
Console.WriteLine(content.Substring(0, 100));
}
}
WebClient类的同步APIDownloadString()会阻塞调用线程
如果 用古早的第一种:异步模式
private static void AsynchronousPattern()
{
WebRequest request = WebRequest.Create(url);
IAsyncResult result = request.BeginGetResponse(ReadResponse, null);
void ReadResponse(IAsyncResult ar)
{
using (WebResponse response = request.EndGetResponse(ar))
{
Stream stream = response.GetResponseStream();
var reader = new StreamReader(stream);
string content = reader.ReadToEnd();
Console.WriteLine(content.Substring(0, 100));
}
}
}
WebClient类没有支持 第一种古早异步模式的 API。这里为了举例,可以用WebRequest类来代替。WebRequest类:- 它的
BeginGetResponse()和EndGetResponse(),使用了第一种古早的异步模式。是同步方法GetResponse()的异步版本 - 它的
Create()方法,创建WebRequest实例 BeginGetResponse()的输入参数是:- 委托类型
AsyncCallback。定义为:入参为IAsyncResult类型, 返回值为void
- 委托类型
- 它的
在这个例子中,BeginGetResponse()的输入参数委托类型AsyncCallback具体实现为内嵌的本地函数:ReadResponse()方法:
- 一旦异步调用完成,就会调用
ReadResponse()方法 - 由于本地函数的闭包功能,在本地函数内可以直接访问本地函数外声明的
WebRequest类的实例request变量 - 另外,假如
ReadResponse()方法没有实现为本地函数,那么则需要将WebRequest类的实例request变量作为入参传进这个方法。这时,可以用IAysncResult类的AsyncState属性 得到 这个通过参数传入的request变量
如果 用第二种: 基于事件的异步模式
private static void EventBasedAsyncPattern()
{
using (var client = new WebClient())
{
//如下提前指定,事件处理程序
//事件处理程序 由拥有同步上线文的线程来执行,在Windows Form, WPF, UWP程序中,那就是UI线程
//所以,在此事件处理程序内,可以访问 UI元素
client.DownloadStringCompleted += (sender, e) =>
{
Console.WriteLine(e.Result.Substring(0, 100));
};
client.DownloadStringAsync(new Uri(url));
}
}
如果 用第三种:基于任务的异步模式
WebClient类提供了一个带后缀“Async”的方法,这个方法实现了基于Task的异步模式
private static async Task TaskBasedAsyncPatternAsync()
{
using (var client = new WebClient())
{
string content = await client.DownloadStringTaskAsync(url);
Console.WriteLine(content.Substring(0, 100));
}
}
思考 总结
对于这三种模式,有一件事需要 重点理解:
异步方法调用结束后,后续的方法 是由哪个线程来调用?
- 对于第一种 古早的异步模式:是在执行异步方法的那个线程中调用
- 对于第二种 基于事件的异步模式:是在拥有同步上下文的线程中调用
- 对于第三种 基于任务的异步模式:默认是在拥有同步上下文的线程中调用,但是也可以配置。