C#异步编程几点需要注意的地方

2020-05-28 13:59:48于海丽

如:对于一个控制台程序,如果只是执行一项计算量较大且耗时较长的任务(或者说,运行时间较长的CPU密集型的任务),那么把该任务单独放在另一个线程中并没有多大好处。因为这样做只能让工作线程始终处于繁忙状态,而主线程则必须一直卡在那里等待工作线程把任务做完。在这种情况下,实际上是用两个线程来完成原本只需要一个线程就能做好的工作,造成了资源的浪费。

避免不必要的上下文切换

目前C#代码中使用async以及await实现的异步方法默认是把await之后的代码放在早前捕获的那个上下文中执行的,这是因为这样做比较稳妥,它最多只会引发几次无谓的上下文切换,而不会使程序出现重大的错误,与之相反,如果系统不把山下文切换回去,那么万一遇到的是只能在特定的上下文中才能执行的代码,那么程序就有可能崩溃。因此,无论有没有必要切换上下文,系统都会切换至早前捕获到的那个上下文,并把await之后的语句放在那个上下文执行。

如果不想让系统做出这样的安排,那么可以调用ConfigureAwait()方法。这表示接下来的那些代码无须放在早前捕获的上下文中执行。例如在很多程序集中,await语句之后的那些代码一般都与上下文无关,因此与,可以调用Task对象的ConfigureAwait()方法告诉系统,在执行完这项任务之后,不必专门把await下面的代码放在早前捕获的上下文中运行。如下所示:

public static async Task<XElement> ReadPacket(string url)
{
	var result=await DownloadAsync(url)
				.ConfigureAwait(false);
	return XElement.Parse(result);			
}

C#语言默认让程序把await下面的语句都放在早前捕获的上下文中执行,这样做虽然较为安全,但是会降低程序的效率。因此为了让用户能够更加顺畅的使用程序,我们应该调整代码的结构,把必须运行在特定上下文的代码剥离出来,并尽量考虑在await语句那里调用ConfigureAwait(false),使得程序可以把语句下面的代码放在默认上下文中运行,而不是切换回早前的上下文。

通过Task对象来进行异步开发

Task(任务)是一种抽象机制,可以用来表示某项工作,于是,就能够把该工作转交给其他资源去完成。Task类型以及与之相关的类与结构体提供了丰富的API,让开发者可以操控Task对象以及由该对象所表示的工作。此外,Task对象自身也具备一些方法与属性,可以用来操作本对象所表示的任务。这些Task对象可以合起来构成一项比较大的任务,他们之间既能够按照顺序执行,也能够平行的执行。

可以通过await语句来确保某些任务之间能够按照一定的顺序执行,也就是说,只有当该语句所要等待的那项工作完毕之后,语句下方的代码才能够执行。