async和await异步的实现原理

异步编程的基本概念

  1. 异步操作:
    • 异步操作是指在不阻塞主线程的情况下执行的操作,允许程序在等待I/O操作完成时继续执行其他任务。
  2. 异步方法:
    • 异步方法使用 async 关键字标记,并且通常返回 Task 或 Task<T。
  3. await 关键字:
    • await 关键字用于等待异步操作完成,但在等待期间不会阻塞线程。它允许程序继续执行其他任务,直到异步操作完成。

实现原理

  1. 任务(Task)
  • Task 类:
    • Task 和 Task<T 是表示异步操作的对象。
    • Task 表示一个不返回结果的异步操作。
    • Task<T 表示一个返回结果的异步操作。
  • 任务状态:
    • Task 可以处于不同的状态,如 Created、Running、RanToCompletion、Faulted、Canceled 等。
    • await 关键字会检查任务的状态,并根据任务的状态决定是否继续执行或等待任务完成。
  1. 状态机(State Machine)
  • 编译器生成的状态机:
    • 当方法中包含 await 关键字时,编译器会将该方法转换为一个状态机。
    • 状态机负责管理异步操作的各个状态,并在异步操作完成时继续执行后续的代码。
  • 状态机的生成:
    • 编译器会生成一个状态机类,该类包含任务的状态和方法的状态信息。
    • 状态机类实现了 IAsyncStateMachine 接口,并且使用 MoveNext 方法来管理异步操作的各个状态。
  1. 上下文(Context)
  • 保存和恢复上下文:
    • await 关键字会保存当前的执行上下文,并在异步操作完成时恢复上下文。
    • 这使得异步方法在等待期间不会阻塞线程,可以在任务完成时继续执行后续的代码。
  • 捕获当前上下文:
    • 编译器会捕获当前的同步上下文(如 SynchronizationContext 或 TaskScheduler),并在异步操作完成时恢复该上下文。
  1. 事件循环(Event Loop)
  • 事件循环:
    • 事件循环是操作系统或运行时环境中的一个机制,用于管理异步操作的执行和调度。
    • 事件循环会不断检查和处理异步操作的状态,确保在异步操作完成时能够继续执行相应的代码。
  • .NET中的事件循环:
    • 在.NET中,事件循环通常由 ThreadPool 或 TaskScheduler 管理。
    • ThreadPool 管理线程池中的线程,用于执行异步操作。
    • TaskScheduler 负责调度任务的执行,确保任务在适当的线程上执行。

具体实现步骤

  1. 定义异步方法:
    • 使用 async 关键字标记方法,并返回 Task 或 Task<T。
    • 异步方法内部可以包含 await 关键字来等待异步操作完成。
  2. 编译器转换:
    • 编译器将 async 和 await 关键字转换为状态机代码。
    • 状态机负责管理异步操作的各个状态,并在异步操作完成时继续执行后续的代码。
  3. 任务调度:
    • 当异步方法被调用时,编译器生成的任务对象会被调度到 ThreadPool 或 TaskScheduler 中。
    • 任务对象会被分配到线程池中的一个线程上执行。
  4. 等待和恢复:
    • 当方法中遇到 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;
				}
			}
		}

详细步骤

  1. 定义异步方法:
    • DownloadDataAsync 方法使用 async 关键字标记,并返回 Task<string。
  2. 编译器转换:
    • 编译器将 DownloadDataAsync 方法转换为一个状态机类。
    • 状态机类实现了 IAsyncStateMachine 接口,并且使用 MoveNext 方法来管理异步操作的各个状态。
  3. 任务调度:
    • 当 DownloadDataAsync 方法被调用时,编译器生成的任务对象会被调度到 ThreadPool 中。
    • 任务对象会被分配到线程池中的一个线程上执行。
  4. 等待和恢复:
    • 当 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;
			}
		}

关键点解释

  1. 状态机类:
    • 编译器生成的状态机类实现了 IAsyncStateMachine 接口。
    • _state 变量表示当前的状态。
    • _builder 变量是 AsyncTaskMethodBuilder<string,用于构建和管理任务。
    • _client、_task1、_task2 等变量用于存储异步操作的状态。
  2. MoveNext 方法:
    • MoveNext 方法用于管理异步操作的各个状态。
    • 根据 _state 的值,执行不同的逻辑。
    • 如果异步操作尚未完成,方法会返回,并允许其他任务继续执行。
    • 如果异步操作完成,方法会继续执行后续的代码,并设置任务的结果或异常。
  3. Awaiter:
    • Awaiter 对象用于检查异步操作的状态。
    • _task1.GetAwaiter() 和 _task2.GetAwaiter() 获取异步操作的 Awaiter 对象。
    • _awaiter1.IsCompleted 和 _awaiter2.IsCompleted 检查异步操作是否完成。
    • _builder.AwaitUnsafeOnCompleted 在异步操作完成时恢复执行。
  4. 异常处理:
    • 如果异步操作抛出异常,方法会捕获异常并设置任务的异常状态。
    • _builder.SetException(ex) 设置任务的异常状态。
  5. 任务调度:
    • 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;
				}
			}
		}

详细步骤

  1. 定义异步方法:
    • ReadFileAsync 方法使用 async 关键字标记,并返回 Task<string。
  2. 创建 StreamReader:
    • 使用 StreamReader 创建一个文件读取器。
  3. 等待 ReadToEndAsync:
    • 使用 await reader.ReadToEndAsync() 等待文件读取完成。
    • 如果文件读取尚未完成,方法会返回,并允许其他任务继续执行。
    • 当文件读取完成时,事件循环会恢复 ReadFileAsync 方法的执行,并继续执行 await 关键字之后的代码。
  4. 返回结果:
    • 读取文件内容后,返回结果给调用者。

总结

  • 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 的工作原理
异步编程的最佳实践:
异步编程最佳实践

posted @ 2025-07-02 19:11  似梦亦非梦  阅读(228)  评论(0)    收藏  举报