Async 和 Await 的工作原理
Posted
技术标签:
【中文标题】Async 和 Await 的工作原理【英文标题】:How Async and Await works 【发布时间】:2014-03-12 10:52:37 【问题描述】:我正在尝试了解 。如何在程序中控制行程。这是我试图理解的代码。
public async Task MyMethod()
Task<int> longRunningTask = LongRunningOperation();
//indeed you can do independent to the int result work here
MySynchronousMethod();
//and now we call await on the task
int result = await longRunningTask;
//use the result
Console.WriteLine(result);
public async Task<int> LongRunningOperation() // assume we return an int from this long running operation
await Task.Delay(5000); //5 seconds delay
return 1;
private void Button_Click_3(object sender, RoutedEventArgs e)
MyMethod();
当按钮单击发生时,将调用 MyMethod()
,并从 MyMethod 调用 LongRunningOperation()
,需要 5 秒才能完成。所以我的问题是
这行是什么意思
任务 longRunningTask = LongRunningOperation();
这是做什么的int result = await longRunningTask;
上面一行可以提交,我们可以像这样构造一行
Task<int> longRunningTask = await LongRunningOperation();
或
int result = await longRunningTask;
请有人给我解释一下上面我不清楚的代码。
1) 如果 longRunningOperation 没有完成并且还在运行,MyMethod() 将返回到它的调用方法,因此主线程不会被阻塞。当 longRunningOperation 完成后,ThreadPool 中的一个线程(可以是任何线程)将返回到 MyMethod() 的先前状态并继续执行(在这种情况下将结果打印到控制台)。
第二种情况是 longRunningOperation 已经完成执行并且结果可用。当到达 await longRunningOperation 时,编译器知道它有结果,并将继续在同一个线程上执行代码。 (在这种情况下,将结果打印到控制台)。
第 1 点对我来说并不像“if the longRunningOperation hasn't finished and is still running, MyMethod() will return to its calling method
”这样的陈述
如果可能的话,更详细地解释第一点。谢谢
【问题讨论】:
想想yield return
,它可以控制循环内的调用代码。
如果您了解Task.ContinueWith(lambda)
的工作原理,您可以将await
之后的代码视为您将放在lambda
中的内容。这个比喻没有考虑同步上下文。
使用此链接。它提供了详细信息和深度信息。 channel9.msdn.com/Series/…
【参考方案1】:
我被教过以下方式,我发现这是一个非常清晰简洁的解释:
//this is pseudocode
async Method()
code;
code;
await something;
moreCode;
当调用Method
时,它会执行其内容(code;
行)直到await something;
。此时,something;
被触发,方法结束,就像return;
在那里一样。
something;
做它需要做的,然后返回。
当something;
返回时,执行回到Method
并从await
继续执行,执行moreCode;
以更简明的方式,发生了以下情况:
-
方法被调用
code;
被执行
something;
被执行,流程回到Method
被调用的地方
在Method
调用之后继续执行
当something;
返回时,流在Method
内部返回
moreCode;
被执行,Method
本身结束(是的,可能还有其他东西 await
-ing 在上面,等等)
【讨论】:
我一直缺少 3 号。这是唯一能以可理解的方式真正解释真正发生的事情的答案。 谢谢!这真的很有帮助 这就是我一直在寻找的答案。简单、清晰、全面。 非常清楚的解释,如果有人可以澄清一下。我不确定我是否理解第 4 步是什么意思?因此,例如:如果 uber 方法(正在调用 method();)就像 ... CodeA; var x = 等待方法();代码B; .基于 set #4 while something;仍在处理(可能在另一个线程上),控制将返回到原始方法并执行 CodeB?是的?我虽然这就是只要有等待代码执行就会停在那里的重点。因此,任务应返回池并为任何其他调用者提供服务。谢谢 我认为这是我读过的对异步操作的最佳解释之一。让它与语言无关是天才。让它稍微有用的是将Method
放在更广泛的程序的上下文中,并显示调用方法之后的代码继续(我知道您在第 3 点中说过,但是一些示例伪代码可以做到更清晰)。特别是,这清楚地说明了为什么不能依赖对异步方法的调用来返回实际对象(因为以下代码可能会在异步操作完成之前运行),而是通常返回类似于 Promise 的东西。【参考方案2】:
我的博客上有一个async
intro,您可能会觉得有帮助。
这段代码:
int result = await LongRunningOperation();
与这段代码基本相同:
Task<int> resultTask = LongRunningOperation();
int result = await resultTask;
所以,是的,LongRunningOperation
是由该方法直接调用的。
当await
操作符传递一个已经完成的任务时,它会提取结果并继续执行该方法(同步)。
当await
操作符传递一个不完整的任务(例如,LongRunningOperation
返回的任务将不完整)时,默认情况下await
将捕获当前上下文并从该方法返回一个不完整的任务。
稍后,当await
任务完成时,该方法的其余部分将安排在该上下文中运行。
这个“上下文”是SynchronizationContext.Current
,除非它是null
,在这种情况下它是TaskScheduler.Current
。如果您在控制台应用程序中运行它,那么上下文通常是线程池上下文,因此async
方法将在线程池线程上恢复执行。但是,如果您在 UI 线程上执行相同的方法,则上下文是 UI 上下文,async
方法将继续在 UI 线程上执行。
【讨论】:
Async 和 Await 在后台线程或前台线程上工作?? 都不是;async
与线程无关。当您调用async
方法时,它会直接在当前线程上调用。当 async
方法在等待任务后恢复时,它会使用我在回答中描述的“上下文”来确定继续在哪个线程上执行。
这完全让我感到困惑......假设 MySyncMethod() 需要 5 秒,而 LongRunnigOp() 需要 3 秒。如果我们有Task<int> resultTask = LongRunningOperation(); MySynchronousMethod(); int result = await resultTask;
,那么总共需要 5 秒,而如果我们有int result = await LongRunningOperation(); MySynchronousMethod();
,那么总共需要 8 秒......不是吗?
@StephenCleary,那么,假设在 ASP.NET 应用程序中,与同步方法的版本相比,在同一行上调用和等待的异步方法有什么用处?例如,为什么这种方法:public async Task<Car> GetCarByIdAsync(int id) return await carRepository.GetByIdAsync(id);
比这种方法更好:public Car GetCarById(int id) return carRepository.GetById(id);
?在调用数据库时,异步方法将释放可由操作系统中的另一个进程使用的线程的唯一优势是什么?我说对了吗?
@StephenCleary - 我不明白的一件事是它如何执行 LongRunningOperation () ?即哪个线程会执行LongRunningOperation()的内容【参考方案3】:
在幕后 C# 编译器实际上将您的代码转换为状态机。它会生成更多代码,因此每次等待任务或异步操作完成时,它都会在幕后继续执行。就您的问题而言,每次异步操作完成时,都会在您最初开始调用异步方法时在调用线程上回调异步方法。例如,它将在您开始的线程上执行您的代码。因此异步操作将在Task
线程上运行,然后结果将在您最初调用该方法的线程上返回并继续执行。
Await
将从Task
或异步操作中获取值,并在返回执行时将其从任务中“拆箱”。在这种情况下它会自动将其放入 int 值中,因此无需存储Task<int>
。
您的代码存在问题,它在LongRunningTask()
上等待,您很可能只想返回没有async
的长任务方法,然后让您的MyMethod
执行等待。
int value = await LongWaitingTask()
Async Await and the Generated StateMachine
async
方法要求您返回 Task
或 void
。
可以更改它,因此当您从执行异步任务返回时,它将使用Task.ConfigureAwait
方法在执行异步任务的线程上执行剩余代码。
【讨论】:
【参考方案4】:这样想可能更容易:
每当您有await
时,编译器就会将您的方法分成两部分:await
之前的一部分和之后的另一部分。
第二部分在第一部分成功完成后运行。
在您的代码中,第一个方法看起来大致相当于:
public async Task MyMethod()
Task<int> longRunningTask = LongRunningOperation();
MySynchronousMethod();
longRunningTask.ContinueWith(t => part2(t.Result));
void part2(int result)
Console.WriteLine(result);
几个重要的注意事项:
-
显然比这复杂得多,因为它应该支持
try-catch 等。此外,它并没有真正使用任务
Task
实际上并没有被直接使用。它正在使用任务的 GetAwaiter()
方法及其 API,或任何其他具有此方法或扩展方法的类。
如果一个方法中有多个等待,它将被拆分多次。
如果MyMethod
被拆分,等待MyMethod
的人如何知道所有部分何时完成?当您的异步方法返回 Task
时,编译器会生成一个特殊的 Task
,它使用状态机跟踪所有内容。
【讨论】:
以上是关于Async 和 Await 的工作原理的主要内容,如果未能解决你的问题,请参考以下文章