C#并发编程之【二】异步编程简介

使用异步编程的原因

使用异步编程的好处我们需要从两方面考虑,即面向终端用户的GUI 程序和服务器端应用,异步编程在这两者之间具备一定的便利。

  • 对于面向终端用户的 GUI程序,异步编程提高了响应能力。我们都遇到过在运行时会临时锁定界面的程序(即所谓的界面卡死),异步编程可以使程序在执行任务时仍能响应用户的输入。
  • 对于服务器端应用,异步编程实现了可扩展性。服务器应用可以利用线程池满足其可扩展性,使用异步编程后,可扩展性通常可以提高一个数量级。
关键字

现代的异步 .NET 程序使用两个关键字:async 和 await。

  • async :关键字加在方法声明上, 它的主要目的是使方法内的 await 关键字生效。如果 async 方法有返回值,应返回 Task< T >;如果没有返回值,应返回 Task。这些 task 类型相当于 future,用来在异步方法结束时通知主程序。
  • await :在 async 方法内部,对它的参数执行一个异步等待。它首先检查操作是否已经完成,如果完成了,就继续运行 (同步方式)。否则,它会暂停 async 方法,并返回,留下一个未完成的 task。一段时间后, 操作完成,async 方法就恢复运行。

一个Async方法中可以有多个同步执行的程序块组成,每个同步程序块之间由 await 语句分隔。用await 语句等待一个任务完成,当该方法在 await 处暂停时,程序就会开始捕捉上下文(context)。
如果当前 SynchronizationContext 不为空,这个上下文就是当前 SynchronizationContext。
如果当前 SynchronizationContext 为空,则这个上下文为当前 TaskScheduler。
该方法会在这个上下文中继续运行。默认情况下,运行 UI 线程时采用 UI 上下文,处理 ASP .NET 请求时采用 ASP .NET 请求上下文,其他很多情况下则采用线程池上下文。
因此,在下面的代码中,每个同步程序块会试图在原始的上下文中恢复运行,但是却无法办到。

async Task DoSomethingAsync()
{	
	int val = 13; 
	// 异步方式等待 1 秒         
	await Task.Delay(TimeSpan.FromSeconds(1)); 
	val *= 2; 
	// 异步方式等待 1 秒         
	await Task.Delay(TimeSpan.FromSeconds(1)); 
	Trace.WriteLine(val);    
 }

注意事项:
如果在 UI 线程中直接调用 一个Async方法(比如上述DoSomethingAsync()),那么这个方法的每个同步程序块都将在此 UI 线程上运行。但是,如果在线程池线程中调用,每个同步程序块将在线程池线程上运行。

要避免这种错误行为,可以在 await 中使用 ConfigureAwait 方法,将参数 continueOn CapturedContext 设为 false。接下来的代码刚开始会在调用的线程里运行,在被 await 暂停后,则会在线程池线程里继续运行:

 async Task DoSomethingAsync()     
 {         
 	int val = 13; 
	// 异步方式等待 1 秒         
	await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); 
	val *= 2; 
	// 异步方式等待 1 秒         
	await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); 
	Trace.WriteLine(val.ToString());     
}

注意事项:
最好的做法是,在核心库代码中一直使用 ConfigureAwait。在外围的用户界 面代码中,只在需要时才恢复上下文。

重要的准则

关于异步方法,还有一条重要的准则:你一旦在代码中使用了异步,最好一直使用。调用 异步方法时,应该(在调用结束时)用 await 等待它返回的 task 对象。一定要避免使用 Task.Wait 或 Task.Result 方法,因为它们会导致死锁。参考一下下面这个方法:

 async Task WaitAsync()    
 {
	 // 这里 awati 会捕获当前上下文……
	 await Task.Delay(TimeSpan.FromSeconds(1));
 	// ……这里会试图用上面捕获的上下文继续执行
 } 
 
void Deadlock()
{
	// 开始延迟
	Task task = WaitAsync(); 
	// 同步程序块,正在等待异步方法完成
	task.Wait();
}

但是如果从 UI 或 ASP .NET 的上下文调用这段代码,就会发生死锁。
这是因为,这两种上下文每次只能运行一个线程。Deadlock 方法调用 WaitAsync 方法,WaitAsync 方法开始调用 delay 语句。然后,Deadlock 方法(同步)等待 WaitAsync 方法完成,同时阻塞了上下文线程。当 delay 语句结束时,await 试图在已捕获的上下文中继续运行 WaitAsync 方法,但这 个步骤无法成功,因为上下文中已经有了一个阻塞的线程,并且这种上下文只允许同时运行一个线程。

这里有两个方法可以避免死锁:

  • 在 WaitAsync 中使用 ConfigureAwait(false) (导致 await 忽略该方法的上下文) 用 await
  • 语句调用 WaitAsync 方法(让 Deadlock 变成一个异步方法)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

洋洋脚踝的金铃响了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值