异步等待并不意味着有多个线程在运行您的代码。
然而,它会减少线程空闲等待进程完成的时间,从而提前完成任务。
每当线程通常需要空闲等待某些事情完成时,例如等待网页下载、数据库查询完成或磁盘写入完成时,异步等待线程将不会空闲等待数据写入/获取,而是寻找其他可以做的事情,并在可等待任务完成后返回。
这在这个Eric Lippert的采访中用厨师类比进行了描述。在中间某处搜索异步等待。
Eric Lippert将异步等待与一个(!)必须制作早餐的厨师进行比较。在他开始烤面包之后,他可以等待面包烤好再烧水泡茶,在水沸腾之前等待再把茶叶放入茶壶中,等等。
异步等待的厨师不会等待烤好的面包,而是烧水,当水加热时,他会把茶叶放入茶壶中。
每当厨师需要空闲等待某些事情时,他都会四处寻找是否可以做其他事情。
异步函数中的线程会做类似的事情。因为该函数是异步的,您知道该函数中有一个等待。实际上,如果您忘记编写等待,则编译器将警告您。
当您的线程遇到await时,它会向上查看其调用堆栈以查看是否可以执行其他操作,直到它看到一个await,再次向上查看调用堆栈,等待所有人都在等待后,他将向下移动调用堆栈并开始空闲等待,直到第一个可等待进程完成。
等待进程完成后,线程将继续处理await之后的语句,直到再次看到await。
可能会有另一个线程继续处理await之后的语句(通过检查线程ID可以在调试器中看到此情况)。但是,这个其他线程具有原始线程的上下文,因此它可以像它是原始线程一样运行。不需要互斥体、信号量、IsInvokeRequired(在winforms中)等。对于您来说,似乎只有一个线程。
有时,您的厨师必须做一些需要花费时间而不是空闲等待的事情,比如切番茄。在这种情况下,聘请另一个厨师并命令他进行切片可能是明智的选择。同时,您的厨师可以继续煮好刚刚煮沸并需要剥皮的鸡蛋。
在计算机术语中,如果您有一些大型计算而不需要等待其他进程,则会出现这种情况。请注意与例如将数据写入磁盘的情况的区别。一旦您的线程已经命令需要将数据写入磁盘,它通常会空闲等待直到数据已经被写入。但是在进行大型计算时不是这种情况。
您可以使用
Task.Run
聘请额外的厨师。
async Task<DateTime> CalculateSunSet()
{
Task<SunsetData> taskFetchData = FetchSunsetData();
Location location = FetchLocation();
SunsetData sunsetData = await taskFetchData;
Task<DateTime> taskBigCalculations = Taks.Run( () => BigCalculations(sunsetData, location);
...
DateTime result = await taskBigCalculations;
return result;
}
await
确实会返回控制权 - 其他所有操作都是继续执行;微妙之处在于,通常调用者都表现得很规矩,知道后台有一些事情正在进行,并相应地采取行动 - 但这并非必须。 - Marc Gravell//一些不依赖于用户对象的工作
呢? - user2051770