Skip to main content

async/wait和ConfigureWait(false)在异步编程中有什么关系?

分类:  .Net技术 标签:  #异步编程 #.Net #基础 发布于: 2023-08-07 22:00:48

我喜欢.Net的最大原因就是.Net是真的提供了很多工具简化编程的难度,并提供了健壮性。尤其是异步编程模式的提出。

.Net使用asyncawait两个关键字来简化异步编程,不过需要注意的是:

  • 异步编程不是并行编程,和我们提到的多线程编程虽然有联系,但是异步编程不是多线程的那种多任务编程。
  • 异步编程要注意编程任务是基于CPU计算多,还是基于IO多,特别是在基于Asp.net Core Genric Host进行后端服务编程时(例如:写基于Windows或者基于Linux的服务程序,不是指Web编程),特别需要注意区分你的任务是基于IO的还是基于CPU计算的任务。基于CPU计算的任务都需要Task.Factory.new放入runtime的线程池中运行。
  • 在基础的编程模型中我们有多进程,多线程分别利用的是CPU的特性,例如分时CPU,多核等等,但是还有一个概念就是可阻塞IO的模型,可以将异步编程模型的基础理念架构在可阻塞IO上。

.Net的异步编程实际上使用起来也很简单:

public async Task<T> dosomethingasync(otherParameters )
{
    await doanothermethodinAsync();
}

使用关键字async/await成对的来定义一个异步方法。需要注意的是必须是成对的出现,假如在方法定义使用了async, 但是方法体并没有使用await来等待Task,那么这个方法就会转为一个同步方法。

很多人不理解:定义了一个异步方法,然后在方法体内使用await等待返回,await之后的代码必须在await的对象返回之后才会开始执行,这怎么就实现了异步了。实际是要从更高的角度去理解,假如在Asp.net Core或者是UI线程里单独执行的时候,类似这样:

//这个时候已经进入了asp.net core或者UI的主线程了。

var task1 = doMethod1Async();
var task2 = doMethod2Async();
var task3 = doMethod3Async();

//do anohter things.

上面这部分代码是在一个单一的线程里运行。如果是在同步的模式下,肯定是需要先调用完方法1,再方法2,方法3.

现在这三个方法都定义为异步方法(记住异步方法的要点,方法体内必须要调用await), 例如方法1:

public async Task<string> doMethod1Async() 
{
    var result = "test";

    result = await doSomeMethodReturnString();

    return result;
}

当在主线程里执行到var task1 = doMethod1Async()时会进入方法1,在方法体内,运行到result = await doSomeMethodReturnString()时,主线程会立即返回,并由runtime保留对于调用方法doMethod1Async的上下文,并继续执行,主线程跟着就开始执行方法2。

所以异步主要就是指这种情况。

那么在主线程如何保证三个task都会执行完呢?

await Task.WhenAll(task1, task2, task3);
//或者
await Task.WhenAny(task1, task2, task3);

这个是理解异步编程的主要知识点。

另外对于异步方法的返回值,TaskTask<T>, 只有事件是返回void

async/awaitConfigureAwait()方法之间的关系

我们上面有谈到,当runtime在调用异步方法的时候,会创建State Machine并保存当时的上下文,例如Asp.net CoreHttpContext, 在UI编程时的同步上下文,当一个异步方法运行完成之后,默认情况下是要返回当时的线程的,使用方法ConfigureAwait(false) 表示某个Task在运行完成之后不用返回当时的线程,返回时有什么线程就用什么线程,甚至新起一个线程。对于大多数情况我们都建议使用ConfigureAwait(false), 防止死锁,特别是在同步环境下调用异步方法的情况下,更明显。

注意
在如今的Asp.net Core框架中构建代码可以不使用这个方法, 框架处理郭类似的问题,但是UI或者Console应用中强烈建议使用。