C# 异步编程
栏目总目录
异步编程
async
和 await
关键字是 C# 5.0 引入的两个非常重要的关键字,它们一起工作,使得异步编程变得简单和直观。
async 关键字
async
关键字用于标记一个方法、lambda 表达式、匿名方法或局部方法作为异步方法。这告诉编译器该方法内部可以使用await
关键字。- 异步方法会隐式返回一个
Task
或Task<T>
对象。如果方法没有返回值(即返回类型为void
),则它应该用于事件处理程序,并应该避免在库或框架代码中使用,因为void
返回类型的方法无法等待或捕获异常。
await 关键字
await
关键字用于等待异步操作的完成。它只能用在被async
修饰的方法内部。- 当编译器看到
await
表达式时,它会将方法的其余部分安排在await
表达式表示的异步操作完成后继续执行。在等待期间,控制权会返回给方法的调用者,允许调用者继续执行其他操作,而不是阻塞等待异步操作的完成。 await
表达式的结果是异步操作的结果。如果操作返回Task<T>
,则await
表达式的类型是T
。如果操作返回Task
,则await
表达式通常用于仅等待操作完成,而不获取其结果。
深入async和await
async和await做了什么
示例
下面是一个简单的异步方法示例,该方法使用 HttpClient
异步获取网页的内容:
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
string content = await GetWebPageAsync("https://www.example.com");
Console.WriteLine(content);
}
static async Task<string> GetWebPageAsync(string url)
{
using (HttpClient client = new HttpClient())
{
HttpResponseMessage response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
throw new HttpRequestException($"Status code does not indicate success: {response.StatusCode}");
}
}
}
}
在这个示例中,Main
方法被标记为 async
,这允许它内部使用 await
。GetWebPageAsync
方法是另一个异步方法,它使用 HttpClient
的 GetAsync
方法来异步获取网页,并使用 await
等待操作完成。然后,它读取响应内容并返回。注意,异常处理也是异步编程中的一个重要方面,上述示例展示了如何在异步方法中抛出和捕获异常。
高效异步
在.NET Core(或现在更常见的.NET 5/6/7等)中实现高效的异步操作主要依赖于理解异步编程模式,特别是如何正确使用async
和await
关键字,以及理解异步操作背后的线程和任务的管理。以下是一些实现高效异步操作的关键点:
1. 理解async
和await
- 使用
async
修饰方法:这表示该方法是异步的,并允许在方法内部使用await
。 - 使用
await
等待异步操作:这会导致当前方法的执行暂停,并在异步操作完成时恢复。重要的是要等待那些真正需要等待的操作,避免不必要的await
调用,因为这会增加延迟。
2. 避免阻塞调用
- 在异步方法中,应避免使用
.Result
或.Wait()
来等待另一个异步操作的结果,因为这会导致死锁,并破坏异步方法的优势。 - 使用
await
来等待异步操作完成,并让线程池来管理线程。
3. 并行与并发
- 并行处理:使用
Task.WhenAll
来并行执行多个不相互依赖的异步操作,这可以显著提高性能,特别是当这些操作是I/O密集型时。 - 并发控制:在需要时使用
SemaphoreSlim
或async
锁(如Mutex
的异步版本)来控制对共享资源的并发访问。
4. 异步资源管理
- 使用
using
语句或async
版本的IDisposable接口(如果可用)来确保异步操作中的资源被正确释放。 - 注意,在
async
方法中,using
语句仍然有效,但Dispose
方法将在等待的异步操作完成后调用。
5. 避免不必要的异步
- 如果一个操作本身很快,并且是CPU密集型的,那么将其包装为异步可能不会带来性能上的好处,反而可能因为额外的开销(如任务调度和上下文切换)而降低性能。
6. 性能测试与调优
- 使用性能测试工具(如BenchmarkDotNet)来测量和比较不同异步实现的性能。
- 根据测试结果进行调优,比如优化数据访问模式、减少不必要的异步调用或改进算法。
7. 使用合适的异步API
- 尽量使用.NET Core提供的异步API,如
HttpClient
、FileStream
的异步方法等,这些API已经过优化,可以提供更好的性能。 - 如果需要,可以自己实现异步方法,但要确保正确管理线程和任务。
8. 监控和日志记录
- 在生产环境中监控异步操作的性能,包括响应时间、吞吐量等关键指标。
- 使用日志记录来跟踪异步操作的执行流程,以便在出现问题时进行调试和故障排查。