async和await异步的实现原理
异步编程的基本概念
- 异步操作:
- 异步操作是指在不阻塞主线程的情况下执行的操作,允许程序在等待I/O操作完成时继续执行其他任务。
- 异步方法:
- 异步方法使用 async 关键字标记,并且通常返回 Task 或 Task<T。
- await 关键字:
- await 关键字用于等待异步操作完成,但在等待期间不会阻塞线程。它允许程序继续执行其他任务,直到异步操作完成。
实现原理
- 任务(Task)
- Task 类:
- Task 和 Task<T 是表示异步操作的对象。
- Task 表示一个不返回结果的异步操作。
- Task<T 表示一个返回结果的异步操作。
- 任务状态:
- Task 可以处于不同的状态,如 Created、Running、RanToCompletion、Faulted、Canceled 等。
- await 关键字会检查任务的状态,并根据任务的状态决定是否继续执行或等待任务完成。
- 状态机(State Machine)
- 编译器生成的状态机:
- 当方法中包含 await 关键字时,编译器会将该方法转换为一个状态机。
- 状态机负责管理异步操作的各个状态,并在异步操作完成时继续执行后续的代码。
- 状态机的生成:
- 编译器会生成一个状态机类,该类包含任务的状态和方法的状态信息。
- 状态机类实现了 IAsyncStateMachine 接口,并且使用 MoveNext 方法来管理异步操作的各个状态。
- 上下文(Context)
- 保存和恢复上下文:
- await 关键字会保存当前的执行上下文,并在异步操作完成时恢复上下文。
- 这使得异步方法在等待期间不会阻塞线程,可以在任务完成时继续执行后续的代码。
- 捕获当前上下文:
- 编译器会捕获当前的同步上下文(如 SynchronizationContext 或 TaskScheduler),并在异步操作完成时恢复该上下文。
- 事件循环(Event Loop)
- 事件循环:
- 事件循环是操作系统或运行时环境中的一个机制,用于管理异步操作的执行和调度。
- 事件循环会不断检查和处理异步操作的状态,确保在异步操作完成时能够继续执行相应的代码。
- .NET中的事件循环:
- 在.NET中,事件循环通常由 ThreadPool 或 TaskScheduler 管理。
- ThreadPool 管理线程池中的线程,用于执行异步操作。
- TaskScheduler 负责调度任务的执行,确保任务在适当的线程上执行。
具体实现步骤
- 定义异步方法:
- 使用 async 关键字标记方法,并返回 Task 或 Task<T。
- 异步方法内部可以包含 await 关键字来等待异步操作完成。
- 编译器转换:
- 编译器将 async 和 await 关键字转换为状态机代码。
- 状态机负责管理异步操作的各个状态,并在异步操作完成时继续执行后续的代码。
- 任务调度:
- 当异步方法被调用时,编译器生成的任务对象会被调度到 ThreadPool 或 TaskScheduler 中。
- 任务对象会被分配到线程池中的一个线程上执行。
- 等待和恢复:
- 当方法中遇到 await 关键字时,编译器生成的代码会检查任务的状态。
- 如果任务尚未完成,方法会返回,并允许其他任务继续执行。
- 当任务完成时,事件循环会恢复方法的执行,并继续执行 await 关键字之后的代码。
示例:异步方法的实现
假设我们有一个方法用于从网络下载数据。使用 async 和 await 关键字可以实现异步下载,而不阻塞主线程。
using System;
using System.Net.Http;
using System.Threading.Tasks;
public class AsyncExample
{
public static async Task Main(string[] args)
{
Console.WriteLine("Starting download...");
string content = await DownloadDataAsync("https://jsonplaceholder.typicode.com/posts/1");
Console.WriteLine("Download completed.");
Console.WriteLine(content);
}
public static async Task<string> DownloadDataAsync(string url)
{
using (HttpClient client = new HttpClient())
{
Console.WriteLine("Downloading data...");
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
string content = await response.Content.ReadAsStringAsync();
Console.WriteLine("Data downloaded.");
return content;
}
}
}
详细步骤
- 定义异步方法:
- DownloadDataAsync 方法使用 async 关键字标记,并返回 Task<string。
- 编译器转换:
- 编译器将 DownloadDataAsync 方法转换为一个状态机类。
- 状态机类实现了 IAsyncStateMachine 接口,并且使用 MoveNext 方法来管理异步操作的各个状态。
- 任务调度:
- 当 DownloadDataAsync 方法被调用时,编译器生成的任务对象会被调度到 ThreadPool 中。
- 任务对象会被分配到线程池中的一个线程上执行。
- 等待和恢复:
- 当 await client.GetAsync(url) 执行时,任务会被挂起,并返回到 Main 方法。
- Main 方法继续执行,输出 "Starting download..."。
- 当 GetAsync 完成时,事件循环会恢复 DownloadDataAsync 方法的执行,并继续执行 await response.Content.ReadAsStringAsync()。
- 当 ReadAsStringAsync 完成时,事件循环会恢复 DownloadDataAsync 方法的执行,并返回下载的内容。
- 最终,Main 方法继续执行,输出 "Download completed." 和下载的内容。
编译器生成的状态机
为了更好地理解 async 和 await 的实现原理,可以查看编译器生成的状态机代码。以下是一个简化的示例,展示编译器如何将异步方法转换为状态机。
原始异步方法
public static async Task<string> DownloadDataAsync(string url)
{
using (HttpClient client = new HttpClient())
{
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
string content = await response.Content.ReadAsStringAsync();
return content;
}
}
编译器生成的状态机代码
编译器会将上述异步方法转换为一个状态机类,类似于以下代码(简化版):
using System;
using System.Net.Http;
using System.Threading.Tasks;
[CompilerGenerated]
private sealed class DownloadDataAsyncStateMachine : IAsyncStateMachine
{
private int _state;
private AsyncTaskMethodBuilder<string> _builder;
private HttpClient _client;
private Task<HttpResponseMessage> _task1;
private HttpResponseMessage _response;
private Task<string> _task2;
private string _content;
private object _awaiter1;
private object _awaiter2;
private string _url;
public AsyncTaskMethodBuilder<string> Builder => _builder;
public void MoveNext()
{
try
{
switch (_state)
{
case -1:
break;
case 0:
_client = new HttpClient();
_task1 = _client.GetAsync(_url);
_state = 1;
_awaiter1 = _task1.GetAwaiter();
if (!_awaiter1.IsCompleted)
{
_builder.AwaitUnsafeOnCompleted(ref _awaiter1, ref this);
return;
}
break;
case 1:
_response = (HttpResponseMessage)_task1.GetAwaiter().GetResult();
_response.EnsureSuccessStatusCode();
_task2 = _response.Content.ReadAsStringAsync();
_state = 2;
_awaiter2 = _task2.GetAwaiter();
if (!_awaiter2.IsCompleted)
{
_builder.AwaitUnsafeOnCompleted(ref _awaiter2, ref this);
return;
}
break;
case 2:
_content = (string)_task2.GetAwaiter().GetResult();
_client.Dispose();
_builder.SetResult(_content);
return;
default:
throw new InvalidOperationException();
}
}
catch (Exception ex)
{
_state = -2;
_builder.SetException(ex);
return;
}
_state = -2;
}
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
_builder.SetStateMachine(stateMachine);
}
}
public class AsyncExample
{
public static async Task Main(string[] args)
{
Console.WriteLine("Starting download...");
string content = await DownloadDataAsync("https://jsonplaceholder.typicode.com/posts/1");
Console.WriteLine("Download completed.");
Console.WriteLine(content);
}
public static Task<string> DownloadDataAsync(string url)
{
AsyncTaskMethodBuilder<string> _builder = AsyncTaskMethodBuilder<string>.Create();
DownloadDataAsyncStateMachine _stateMachine = new DownloadDataAsyncStateMachine
{
_url = url,
_builder = _builder,
_state = -1
};
_builder.Start(ref _stateMachine);
return _builder.Task;
}
}
关键点解释
- 状态机类:
- 编译器生成的状态机类实现了 IAsyncStateMachine 接口。
- _state 变量表示当前的状态。
- _builder 变量是 AsyncTaskMethodBuilder<string,用于构建和管理任务。
- _client、_task1、_task2 等变量用于存储异步操作的状态。
- MoveNext 方法:
- MoveNext 方法用于管理异步操作的各个状态。
- 根据 _state 的值,执行不同的逻辑。
- 如果异步操作尚未完成,方法会返回,并允许其他任务继续执行。
- 如果异步操作完成,方法会继续执行后续的代码,并设置任务的结果或异常。
- Awaiter:
- Awaiter 对象用于检查异步操作的状态。
- _task1.GetAwaiter() 和 _task2.GetAwaiter() 获取异步操作的 Awaiter 对象。
- _awaiter1.IsCompleted 和 _awaiter2.IsCompleted 检查异步操作是否完成。
- _builder.AwaitUnsafeOnCompleted 在异步操作完成时恢复执行。
- 异常处理:
- 如果异步操作抛出异常,方法会捕获异常并设置任务的异常状态。
- _builder.SetException(ex) 设置任务的异常状态。
- 任务调度:
- AsyncTaskMethodBuilder<string.Create() 创建任务构建器。
- DownloadDataAsyncStateMachine 实例化状态机类。
- _builder.Start(ref _stateMachine) 启动状态机。
- _builder.Task 返回任务对象。
为什么使用 async 和 await?
- 非阻塞操作:
- await 关键字不会阻塞线程,允许程序在等待I/O操作完成时继续执行其他任务。
- 简化代码:
- async 和 await 关键字简化了异步代码的编写,避免了复杂的回调和状态管理。
- 提高响应性:
- 在UI应用程序中,使用 async 和 await 可以保持界面的响应性,避免界面冻结。
- 更好的资源利用:
- 通过非阻塞操作,可以更有效地利用系统资源,特别是在处理大量I/O操作时。
示例:使用 async 和 await 处理文件读写
using System;
using System.IO;
using System.Threading.Tasks;
public class FileAsyncExample
{
public static async Task Main(string[] args)
{
Console.WriteLine("Starting file read...");
string content = await ReadFileAsync("example.txt");
Console.WriteLine("File read completed.");
Console.WriteLine(content);
}
public static async Task<string> ReadFileAsync(string filePath)
{
using (StreamReader reader = new StreamReader(filePath))
{
Console.WriteLine("Reading file...");
string content = await reader.ReadToEndAsync();
Console.WriteLine("File read.");
return content;
}
}
}
详细步骤
- 定义异步方法:
- ReadFileAsync 方法使用 async 关键字标记,并返回 Task<string。
- 创建 StreamReader:
- 使用 StreamReader 创建一个文件读取器。
- 等待 ReadToEndAsync:
- 使用 await reader.ReadToEndAsync() 等待文件读取完成。
- 如果文件读取尚未完成,方法会返回,并允许其他任务继续执行。
- 当文件读取完成时,事件循环会恢复 ReadFileAsync 方法的执行,并继续执行 await 关键字之后的代码。
- 返回结果:
- 读取文件内容后,返回结果给调用者。
总结
-
async 和 await:
- async 关键字:用于标记异步方法,并返回 Task 或 Task<T。
- await 关键字:用于等待异步操作完成,不会阻塞线程。
-
实现原理:
- 任务(Task):表示异步操作。
- 状态机(State Machine):编译器将异步方法转换为状态机类。
- 上下文(Context):保存和恢复执行上下文。
- 事件循环(Event Loop):管理异步操作的执行和调度。
-
优势:
- 非阻塞操作:不会阻塞线程,提高响应性。
- 简化代码:简化异步代码的编写。
- 提高响应性:保持界面的响应性,避免界面冻结。
- 更好的资源利用:更有效地利用系统资源。
参考资源
Microsoft Docs - 异步编程:
异步编程文档
Async Patterns:
异步模式文档
深入理解 async 和 await:
深入理解 async 和 await
async 和 await 的工作原理:
深入理解 async 和 await 的工作原理
异步编程的最佳实践:
异步编程最佳实践

浙公网安备 33010602011771号