C#知识整理-异步编程
.NET 提供了执行异步操作的三种模式:
- 基于任务的异步模式 (TAP),该模式使用单一方法表示异步操作的开始和完成。TAP 是在 .NET Framework 4 中引入的。这是在 .NET 中进行异步编程的推荐方法。C# 中的async和await关键词以及 Visual Basic 中的Async和Await运算符为 TAP添加了语言支持。
- 基于事件的异步模式 (EAP),是提供异步行为的基于事件的旧模型。 这种模式需要后缀为Async的方法,以及一个或多个事件、事件处理程序委托类型和EventArg派生类型。 EAP 是在 .NET Framework 2.0 中引入的。 建议新开发中不再使用这种模式。
- 异步编程模型 (APM) 模式(也称为IAsyncResult模式),这是使用IAsyncResult接口提供异步行为的旧模型。 在这种模式下,同步操作需要Begin和End方法(例如,BeginWrite和EndWrite以实现异步写入操作)。
这里只讨论TAP模式
Task/async/await
关键字:
async:接口无法使用async,配合await使用将方法包装为状态机
await:等待一个异步方法完成,可能会来到一个新的线程上
Task:异步任务
async Task/Task: 定义异步方法
async void:无返回值的异步方法,一般async void只有在定义wpf event方法会使用到,async void有一个问题就是无法聚合异常,在调用async void的方法时异常无法捕获可以想见会引发很多问题,所以一般被调用的方法使用async Task
这次的代码使用WPF做案例
//最常见用到的异步调用的,就是Web API的调用
//先定义一个WebAPI
[Route("api/[controller]")]
[ApiController]
public class DemoController : ControllerBase
{
[HttpGet]
public string Get()
{
return "Test Get API";
}
}
//在WPF里
//添加3个组件
<Grid>
<Button Content="GetApiContent" HorizontalAlignment="Center" Margin="0,81,0,0" VerticalAlignment="Top" Click="Button_Click"/>
<TextBlock Name="text_block" HorizontalAlignment="Center" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Center" Height="192" Width="540"/>
<TextBox HorizontalAlignment="Left" Margin="143,83,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
</Grid>
//对应的cs文件
private async void Button_Click(object sender, RoutedEventArgs e)
{
var url = "https://localhost:7163/api/Demo";
HttpClient client = new HttpClient();
var result = await client.GetAsync(url).ConfigureAwait(true);
string content = string.Empty;
if (result.IsSuccessStatusCode)
{
content = await result.Content.ReadAsStringAsync();
}
text_block.Text += content;
var getHeavyJobResult = await HeavyJobAsync();
text_block.Text += getHeavyJobResult;
}
private async Task<int> HeavyJobAsync() {
await Task.Delay(2000).ConfigureAwait(false);//.ConfigureAwait(true);;
return 100;
}
这段代码还可以这样写
//在同步方法中调用异步方法
private void Button_Click2(object sender, RoutedEventArgs e)
{
var url = "https://localhost:7163/api/Demo";
HttpClient client = new HttpClient();
var result = client.GetAsync(url).GetAwaiter().GetResult();
string content = string.Empty;
if (result.IsSuccessStatusCode)
{
content = result.Content.ReadAsStringAsync().GetAwaiter().GetResult();
}
text_block.Text += content;
//getAwaiter和await一样会释放当前线程
var getHeavyJobResult = HeavyJobAsync().GetAwaiter().GetResult();
text_block.Text += getHeavyJobResult;
//不建议使用.Result来写,这里会阻塞线程
var getHeavyJobResult2 = HeavyJobAsync().Result;
text_block.Text += getHeavyJobResult2;
}
private async Task<int> HeavyJobAsync() {
//大家可以尝试看看ConfigureAwait(false)和ConfigureAwait(true)
有什么不同
await Task.Delay(2000).ConfigureAwait(false);//.ConfigureAwait(true);
return 100;
}
ConfigureAwait在WPF中可以避免UI线程上的死锁
- 如果异步方法在UI线程上启动,并且没有使用ConfigureAwait(false),那么当await操作完成后,它会继续在UI线程上执行,这可能导致死锁,因为UI线程可能正在等待用户输入或其他操作。
- 使用ConfigureAwait(false)可以避免不必要的上下文切换。

浙公网安备 33010602011771号