await (async) Task 和 await Task 有什么区别?

What's the difference between await (async) Task and await Task?

等待异步任务和等待简单任务有什么区别?

更好地理解我的疑问的示例代码:

WEBAPI 调用:

public async Task<IHttpActionResult> GetFiles()
{
   //Simple call for clean code, i think
   var t = DownloadFile(1);
   return Ok(await t);
}

案例 A:

public static async Task<File> DownloadFile(int fileId){
  //Some checks if i can download the file

  //Some other checks if the file exist

  return await TakeFile(fileId);
}

案例 B:

//In this case i can use ref and out parameters and await the task!

public static Task<File> DownloadFile(int fileId){
  //Some checks if i can download the file

  //Some other checks if the file exist

  var t = Task.Run(() => TakeFile(fileId));
  t.Wait();
  return Task.FromResult(t.Result);
}

performance/hangs的区别在哪里?

编辑:我只使用以不同方式调用的异步代码

案例 B 不是异步等待。

如果你有一个线程,并且这个线程必须开始一个相当漫长的过程,在这个过程中它除了等待之外别无他法,通常是因为其他人/其他东西执行了操作,那么你的线程可能会决定做其他事情而不是等待操作完成。

这方面的典型操作是将数据写入磁盘、请求 Internet 页面、进行数据库查询等。在非常低的级别上,您的线程只能等待操作完成。

例如,将数据写入文件时,线程的世界会在它命令硬件写入数据时结束。您的线程不必移动硬盘中的写入臂,等待正确的扇区位于写入臂下,发送写入信号等。您的线程只能等待完成。

这期间你的线程可以开始干别的事情,有空的时候可以检查写操作是否完成,然后执行写操作后的语句。

这种情况在 this interview with Eric Lippert. 中的厨房类比中有所描述,其中厨师不等待茶水煮沸,而是开始切面包。在中间某处搜索 async-await

无论何时调用异步函数,您都可以确定有等待。事实上,如果您编写异步函数但忘记在其中某处等待,您的编译器会抱怨。

只要您的线程进入异步函数,它就会继续工作,直到看到等待。这表明线程不应在等待的任务完成之前执行 await 之后的语句 returns.

通常您的线程不会执行任何操作。但是在 async-await 中,您的线程会在其调用堆栈中上升以在调用之后执行函数,直到它看到等待。它再次进入调用堆栈,执行函数直到它看到 await 等

在大家等待之后,线程不能做任何事情,返回到线程池。如果我们正在等待的进程(硬盘写入)完成,一个线程被提取到线程池中。此线程将继续执行 await 之后的语句,直到它再次看到 await。

这在Stephen Cleary: There is no thread

的文章中有描述

您经常会在异步调用后立即看到等待:

var fetchedItems = await stream.ReadAsync();

在这种情况下,等待是在调用之后立即进行的。在 ReadAsync 完成之前,线程不会在此函数中执行太多操作。

但有时您的函数并不需要立即得到结果:

var fetchTask = stream.ReadAsync()
// because there is no await, instead of doing nothing the thread can do the following:
DisplayWaitIcon();
CalculateSomething();

// now the thread needs the result. So it starts awaiting:
var fetchedItems = await fetchTask;
// here we know that the ReadAsync is finished,
// and the returned items are available in fetchedItems.
ProcessFetchedItems(fetchedItems);

你看,在我的故事中,只有一个线程在做所有的事情。如果仔细观察,它不一定是执行所有操作的同一个线程,它可能是不同的线程。

如果您调查 ThreadId,这可以在调试器中看到。这个其他线程具有原始线程的“上下文”,效果是它可以像原始线程一样工作。您不必担心多线程、互斥锁、竞争条件等。对于程序的设计者来说,就好像一个线程可以完成所有的事情。

但是案例 B 不是异步等待。在这里,您从可用线程池中订购一个线程来做某事。与此同时,您的线程可以自由地做其他事情,直到它等待另一个线程执行的任务完成。因为您的函数不是异步的,所以无论何时您的线程开始等待,它都不会进入其调用堆栈以查看它是否可以做其他事情,它实际上是在等待并且什么都不做,直到任务完成。

This article about async-await 由非常有帮助的 Stephen Cleary 撰写,帮助我了解如何使用 async-await