等待异步方法在后台做什么?
What does awaiting an asynchronous method do in background?
我已经阅读了有关异步等待的各种文章,并且我正在尝试深入了解等待异步。我的问题是我发现等待异步方法不会创建新线程,而只是使 UI 响应。如果使用 await async 时没有时间增益,因为没有使用额外的线程。
到目前为止我所知道的是只有 Task.Run() 创建一个新线程。
Task.WhenAll() 或 Task.WhenAny() 也是如此吗?
假设我们有这段代码:
async Task<int> AccessTheWebAsync()
{
using (HttpClient client = new HttpClient())
{
Task<string> getStringTask = client.GetStringAsync("https://docs.microsoft.com");
DoIndependentWork();
string urlContents = await getStringTask;
return urlContents.Length;
}
}
我期待什么:
创建getStringTask任务时,另一个线程会复制当前上下文并开始执行GetStringAsync方法。
当等待 getStringTask 时,我们将查看另一个线程是否完成了他的任务,如果没有,控件将返回 AccessTheWebAsync() 方法的调用者,直到另一个线程完成它的任务以恢复控制.
所以我真的不明白在等待任务时如何没有创建额外的线程。有人可以解释等待任务时到底发生了什么吗?
您的基本假设 — Task
始终在线程上 运行 — 确实是错误的。一个简单的反例是基于计时器的任务,它根本没有 运行:它只是订阅计时器并在计时器触发时将任务状态设置为已完成。
更有用和更实用的任务示例 运行ning 无处不在 — 网络请求:它们发送请求,订阅传入的答案,然后停止 运行ning,为另一个线程释放线程工作*.
所以让我们考虑一下您的实际问题。
What i knew so far is that only Task.Run() create a new thread. Is this also true for Task.WhenAll() or Task.WhenAny() ?
不,Task.WhenAll
不会创建任何新线程。它会等待已经存在的任务完成,不管它们 运行 在哪里(也不管它们是否 运行 在 any 线程中!)。
Task.WhenAll
创建的任务不在任何特定线程中 运行!它只是检测底层任务何时完成,并且在所有任务准备就绪后,它自己也完成。 Task.WhenAll
不需要任何线程来执行此操作。
When creating the getStringTask Task, another thread will copy the current context and start executing the GetStringAsync method.
调用像GetStringAsync
这样的异步方法,如我们之前所见,不会在任何特定线程上执行。 GetStringAsync
的代码设置了一些东西,以便它在答案到来时(可能在线程池线程上)取回控制权,并将控制权交还给您。准备工作可以在当前线程完美完成,不会占用太多时间*。
*免责声明:这是一种简化,实际上网络异步请求完成的操作顺序要复杂得多。
一篇对我理解 async-await 有很大帮助的文章是 this interview with Eric Lippert,他在文中将 async-await 比作做早餐的厨师。在中间某处搜索 async-await。
如果一个厨师必须做早餐,而他只是在烤面包机里放了一些面包,他不会空等面包烤好,而是开始四处看看是否可以做其他事情,例如泡茶的开水。
当您看到 async-await 时,会发生类似的情况。如果你调用一个异步函数,你知道里面的某个地方是一个等待。事实上,如果您忘记在异步函数中等待,您的编译器会警告您。
一旦您的线程看到 await,它不会空闲地等待可等待的任务完成,而是四处看看是否可以做其他事情。它可以向上调用堆栈查看调用者之一是否尚未等待并执行这些语句,直到它看到等待。再次向上调用堆栈并执行语句,直到看到 await。
无法保证在未等待的异步调用之后继续执行语句的线程与原始线程相同。但是因为这个线程具有相同的 "context",所以您可以将其视为同一个线程。不需要临界区之类的。
Console.Writeline(Thread.CurrentThread.ManagedThreadId);
// async call to the text reader to read a line; don't await
var taskReadLine = myTextReader.ReadLineAsync()
// because I did not await, the following will be executed as soon as a thread is free
Console.Writeline(Thread.CurrentThread.ManagedThreadId);
...
// we need the read line; await for it
string readLine = await taskReadLine;
Console.Writeline(Thread.CurrentThread.ManagedThreadId);
ProcessReadLine(readLine);
无法保证执行 DoSomething 的线程与用于调用 ReadLineAsync 的线程相同。如果您在一个简单的测试程序中执行代码,您获得多个线程 ID 的可能性很高。
在等待结果之前,您的代码不应依赖于要执行的异步函数中的任何语句:
async Task<int> DoIt()
{
this.X = 4;
await DoSomethingElseAsync(this.X);
return 5;
}
async Task CallDoItAsync()
{
this.X = 0;
var taskDoIt = DoIt();
// you didn't await, it is not guaranteed that this.X already changed to 4
...
int i = await taskDoIt();
// now you can be certain that at some moment 4 had been assigned to this.X
创建任务对象不会创建线程。创建线程的开销相当大。因此,您的进程有一个线程池,其中包含多个线程。空闲的线程被放入池中,并可根据请求执行其他操作。一旦您的进程需要一个线程,它就会从线程池中获取一个可用线程并将其调度为 运行.
我不确定如果池中没有可用线程会发生什么情况。我猜你的函数只需要等待一个可用的线程。
您可以使用静态线程池访问线程池class。
ThreadPool.GetMaxThreads (out int workerThreads, out int completionPortThreads);
++workerThreads;
++completionPortThreads;
bool success = ThreadPool.SetMaxThreads (workerThreads, completionPortThreads);
更换线程池要非常小心!
有人说 async-await 只是为了保持 UI 响应有用,但下面显示它也可以提高处理速度。
非异步:
void CopyFile(FileInfo infile, FileInfo outFile)
{
using(var textReader = inFile.OpenText())
{
using (var textWriter = outFile.CreateText())
{
// Read a line. Wait until line read
var line = textReader.ReadLine();
while (line != null)
{
// Write the line. Wait until line written
textWrite.WriteLine(line);
// Read the next line. Wait until line read
line = textReader.ReadLine();
}
}
}
}
你看到所有的等待。幸运的是 TextReader 和 TextWriter 做缓冲数据,否则我们真的不得不等到数据写入后才能读取下一行
async Task CopyFileAsync(FileInfo infile, FileInfo outFile)
{
using(var textReader = inFile.OpenText())
{
using (var textWriter = outFile.CreateText())
{
// Read a line. Wait until line read
var line = await textReader.ReadLineAsync();
while (line != null)
{
// Write the line. Don't wait until line written
var writeTask = textWrite.WriteLineAsync(line);
// While the line is being written, I'm free to read the next line.
line = textReader.ReadLine();
// await until the previous line has been written:
await writeTask;
}
}
}
}
正在写入一行时,我们已经尝试读取下一行。这样可以提高处理速度。
If it's like that there's no time gain when using await async since no extra thread is used.
这是正确的。 async
和 await
本身并不直接使用线程。它们的目的是释放调用线程。
What i knew so far is that only Task.Run() create a new thread. Is this also true for Task.WhenAll() or Task.WhenAny() ?
否; Task.WhenAll
和 Task.WhenAny
都不直接使用任何线程。
When creating the getStringTask Task, another thread will copy the current context and start executing the GetStringAsync method.
没有。 GetStringAsync
在当前线程上同步调用,就像任何其他方法一样。同样,它 return 是一项未完成的任务。
When awaiting getStringTask, we will see if the other thread has completed his task, if not the control will be back the caller of AccessTheWebAsync() method until the other thread complets it's task to resume the control.
关闭,只是没有其他线程。 await getStringTask
将检查 任务 是否完成;如果不是,那么它将 return 来自 AccessTheWebAsync
.
的未完成任务
Can someone exmplain what exactly happening when awaiting a Task ?
我建议阅读我的 async
intro 了解更多详情。
I've red various articles about async await and i'm trying to understand the await async in depth.
崇高的追求。
My problem is that i found out that awaiting an asyncronous method doesn't creat a new thread, it rather just make the UI responsive.
正确。认识到await
意味着异步等待是非常重要的。它 而不是 意味着 "make this operation asynchronous"。这意味着:
- 这个操作已经是异步的。
- 如果操作完成,获取其结果
- 如果操作未完成,return 给调用者并将此工作流的剩余部分分配为未完成操作的继续。
- 当未完成的操作完成时,它将安排继续执行。
If it's like that there's no time gain when using await async since no extra thread is used.
这是不正确的。你没有正确考虑时间赢。
想象一下这个场景。
- 想象一个没有 ATM 的世界。我在那个世界长大。那是一段奇怪的时光。所以在银行通常会有人排队等候存款或取款。
- 假设这家银行只有一名出纳员。
- 现在想象一下,银行只接受和发放单张美元钞票。
假设有三个人在排队,他们每人要十美元。你排在队伍的最后,你只想要一美元。这里有两个算法:
- 给排在第一位的人一美元。
- [这样做十次]
- 给排在第二位的人一美元。
- [这样做十次]
- 给排在第三位的人一美元。
- [这样做十次]
- 给你美元。
每个人要等多久才能拿到所有的钱?
- 第一个人等待 10 个时间单位
- 人二等20
- 人三等30
- 你等31.
这是一个同步算法。异步算法是:
- 给排在第一位的人一美元。
- 给排在第二位的人一美元。
- 给排在第三位的人一美元。
- 给你美元。
- 给排在第一位的人一美元。
- ...
这是一个异步解决方案。现在大家要等多久?
- 每人拿到 10 美元等 30 美元左右。
- 你等了 4 个单位。
大型作业的平均吞吐量较低,但小型作业的平均吞吐量高得多。 那是胜利。此外,每个人的time-to-first-dollar在异步工作流中较低,即使时间最后一美元 对于大型工作来说更高。另外,异步系统公平;每个作业大约等待(作业大小)x(作业数量)。在同步系统中,有些作业几乎没有等待时间,有些作业等待时间很长。
另一个胜利是:出纳员很贵;该系统雇用了一名出纳员,并为小型工作提供了良好的吞吐量。如您所述,要在同步系统中获得良好的吞吐量,您需要雇用更多出纳员,这很昂贵。
Is this also true for Task.WhenAll() or Task.WhenAny() ?
他们不创建线程。他们只是完成一堆任务,并在 all/any 个任务完成后完成。
When creating the getStringTask Task, another thread will copy the current context and start executing the GetStringAsync method.
绝对不是。该任务已经是异步的,因为它是一个 IO 任务,所以不需要线程。 IO 硬件已经是异步的。没有新员工入职
When awaiting getStringTask, we will see if the other thread has completed his task
不,没有其他线程。我们查看 IO 硬件是否已完成其任务。没有线程。
当你把一片面包放进烤面包机,然后去查看你的电子邮件时,烤面包机里没有人运行正在烤面包机。事实上,您可以启动一个异步作业,然后在它工作时离开并做其他事情,这是因为您拥有 本质上是异步的专用硬件 。网络硬件也是如此,就像烤面包机一样。 没有线程。你的烤面包机上没有小人 运行。它 运行 本身。
if not the control will be back to the caller of AccessTheWebAsync() method until the other thread completes its task to resume the control.
同样,没有其他线程。
但是控制流程是正确的。 如果任务完成,则获取任务的值。如果未完成,则在将当前工作流的剩余部分分配为任务的延续后,将 returns 控制给调用者。 当任务完成时,延续被安排为 运行.
i really don't get how no extra thread is created when awaiting a Task.
再一次,回想一下你生命中的每一次,当你因为被阻塞而停止执行某项任务时,做了一段时间的其他事情,然后当你被解除阻塞时再次开始执行第一个任务。 你有没有请工人?当然没有。然而,当吐司还在烤面包机里时,你却以某种方式成功地制作了鸡蛋。基于任务的异步只是将 real-world 工作流程放入软件中。
从来没有让我感到惊讶的是,你们今天的孩子们如何用奇怪的音乐表现得像线程一样一直存在,而且 没有其他方法可以进行多任务处理。我学会了如何在没有 有 线程的操作系统中编程。如果你想让两件事看起来同时发生,你必须建立自己的异步;它没有内置到语言或 OS 中。然而我们成功了。
协作式 single-threaded 异步对世界来说是 return ,就像我们错误地将线程引入控制流结构之前一样;一个更优雅、更简单的世界。 await 是协作式多任务系统中的暂停点。 在 pre-threading Windows 中,您会为此调用 Yield()
,而我们没有没有创建延续和闭包的语言支持;你希望状态在 yield 中持续存在,你编写了代码来做到这一点。你们都过得很轻松!
Can someone explain what exactly happening when awaiting a Task ?
正是你说的,只是没有线程。检查任务是否完成;如果它完成了,你就完成了。如果不是,则将工作流的其余部分安排为任务的延续,并且 return。这就是 await
所做的全部。
I just want to confirm something. Is it always the case that there's no thread created when awaiting a task?
我们在设计功能时担心人们会相信 "await" 会对紧随其后的 call 有所作为。 它没有。 Await 对 return 值 做了一些事情。同样,当您看到:
int foo = await FooAsync();
你应该在心里看到:
Task<int> task = FooAsync();
if (task is not already completed)
set continuation of task to go to "resume" on completion
return;
resume: // If we get here, task is completed
int foo = task.Result;
使用 await 调用方法并不是一种特殊的调用。 "await" 不会启动线程或类似的东西。它是一个运算符,对 returned 的值进行运算。
所以等待任务 不会启动线程。等待任务 (1) 检查任务是否完成,(2) 如果没有完成,则将方法的剩余部分分配为任务的继续,以及 returns。就这样。 Await 不做任何创建线程的事情。现在,也许 调用的方法 启动了一个线程;那是这是公事。这与 await 无关,因为等待直到调用 returns 之后才会发生。 被调用函数不知道其 return 值正在等待.
Let's say we await a CPU bound task that does heavy calculations. What i know so far is a I/O bound code it will be executed on low level CPU components (much lower than threads) and only use a thread briefly to notify the context about the finished Task status.
关于上面对 FooAsync 的调用,我们知道它是异步的,并且它 return 是一个任务。我们不知道如何它是异步的。那就是 FooAsync 业务的作者!但是 FooAsync 的作者可以使用三种主要技术来实现异步。正如您所注意到的,两种主要技术是:
如果任务是high-latency,因为它需要在另一台CPU的当前机器上完成长时间的计算,那么获得一个工作线程是有意义的,并且启动线程在另一个 CPU 上执行工作。工作完成后,如果任务是在 UI 线程或另一个工作线程上创建的,则相关任务可以将其继续安排到 运行 回到 UI 线程上,因为合适。
如果任务是 high-latency 因为它需要与慢速硬件(如磁盘或网络)通信,那么正如您所注意到的,没有线程。 Special-purpose 硬件异步执行任务,操作系统提供的中断处理最终负责在正确的线程上安排任务完成。
异步的第三个原因不是因为您正在管理一个 high-latency 操作,而是因为您将算法分解成小部分并将它们放在工作队列中.也许您正在制作自己的自定义调度程序,或者实现一个参与者模型系统,或者尝试进行无堆栈编程,或者其他任何事情。没有线程,没有IO,但是有异步
因此,等待 不会在工作线程上做一些事情运行。 调用启动工作线程的方法会在工作线程运行 上产生一些东西。让您调用的 方法 决定是否创建工作线程。 异步方法已经是异步的。您无需对它们执行任何操作即可 使 它们异步。 Await 不会使任何事情异步。
Await 的存在只是为了让开发人员更容易检查异步操作是否已完成,并在未完成时将当前方法的其余部分注册为继续。这就是它的用途。同样,await 不会创建异步。 Await 帮助您构建异步工作流。 await 是工作流中的一个点,在该点上,必须先完成异步任务,然后工作流才能继续。
I also know that we use Task.Run() to execute CPU bound code to look for an available thread in thread pool. Is this true ?
没错。如果你有一个同步方法,并且你知道它是 CPU 绑定的,你希望它 b异步的,而且你知道这个方法在另一个线程上运行是安全的,那么Task.Run会找一个worker线程,调度delegate在worker线程上执行,给你一个task代表异步操作。您应该只使用 (1) 非常 long-running,比如超过 30 毫秒,(2) CPU 绑定,(3) 可以安全调用另一个线程的方法。
如果您违反其中任何一条,就会发生不好的事情。如果你雇用一名工人来完成不到 30 毫秒的工作,那么,想想现实生活吧。如果你有一些计算要做,那么购买广告、面试候选人、雇用某人、让他们将三打数字加在一起然后解雇他们有意义吗? 雇用一个工作线程是昂贵的。如果雇用线程比自己完成工作更昂贵,那么雇用线程根本不会带来任何性能提升;你会让事情变得更糟。
如果您雇用一名工人来执行 IO 绑定任务,那么您所做的就是雇用一名工人在邮箱旁坐 年 并在邮件到达时大喊大叫。 这不会使邮件到达更快。它只是浪费了本可以用于其他问题的工人资源。
如果你雇用一名工人来完成一项非线程安全的任务,好吧,如果你雇用两名工人并告诉他们同时驾驶同一辆车到两个不同的地点,他们将会他们在高速公路上争夺方向盘时撞毁了汽车。
我已经阅读了有关异步等待的各种文章,并且我正在尝试深入了解等待异步。我的问题是我发现等待异步方法不会创建新线程,而只是使 UI 响应。如果使用 await async 时没有时间增益,因为没有使用额外的线程。
到目前为止我所知道的是只有 Task.Run() 创建一个新线程。 Task.WhenAll() 或 Task.WhenAny() 也是如此吗?
假设我们有这段代码:
async Task<int> AccessTheWebAsync()
{
using (HttpClient client = new HttpClient())
{
Task<string> getStringTask = client.GetStringAsync("https://docs.microsoft.com");
DoIndependentWork();
string urlContents = await getStringTask;
return urlContents.Length;
}
}
我期待什么:
创建getStringTask任务时,另一个线程会复制当前上下文并开始执行GetStringAsync方法。
当等待 getStringTask 时,我们将查看另一个线程是否完成了他的任务,如果没有,控件将返回 AccessTheWebAsync() 方法的调用者,直到另一个线程完成它的任务以恢复控制.
所以我真的不明白在等待任务时如何没有创建额外的线程。有人可以解释等待任务时到底发生了什么吗?
您的基本假设 — Task
始终在线程上 运行 — 确实是错误的。一个简单的反例是基于计时器的任务,它根本没有 运行:它只是订阅计时器并在计时器触发时将任务状态设置为已完成。
更有用和更实用的任务示例 运行ning 无处不在 — 网络请求:它们发送请求,订阅传入的答案,然后停止 运行ning,为另一个线程释放线程工作*.
所以让我们考虑一下您的实际问题。
What i knew so far is that only Task.Run() create a new thread. Is this also true for Task.WhenAll() or Task.WhenAny() ?
不,Task.WhenAll
不会创建任何新线程。它会等待已经存在的任务完成,不管它们 运行 在哪里(也不管它们是否 运行 在 any 线程中!)。
Task.WhenAll
创建的任务不在任何特定线程中 运行!它只是检测底层任务何时完成,并且在所有任务准备就绪后,它自己也完成。 Task.WhenAll
不需要任何线程来执行此操作。
When creating the getStringTask Task, another thread will copy the current context and start executing the GetStringAsync method.
调用像GetStringAsync
这样的异步方法,如我们之前所见,不会在任何特定线程上执行。 GetStringAsync
的代码设置了一些东西,以便它在答案到来时(可能在线程池线程上)取回控制权,并将控制权交还给您。准备工作可以在当前线程完美完成,不会占用太多时间*。
*免责声明:这是一种简化,实际上网络异步请求完成的操作顺序要复杂得多。
一篇对我理解 async-await 有很大帮助的文章是 this interview with Eric Lippert,他在文中将 async-await 比作做早餐的厨师。在中间某处搜索 async-await。
如果一个厨师必须做早餐,而他只是在烤面包机里放了一些面包,他不会空等面包烤好,而是开始四处看看是否可以做其他事情,例如泡茶的开水。
当您看到 async-await 时,会发生类似的情况。如果你调用一个异步函数,你知道里面的某个地方是一个等待。事实上,如果您忘记在异步函数中等待,您的编译器会警告您。
一旦您的线程看到 await,它不会空闲地等待可等待的任务完成,而是四处看看是否可以做其他事情。它可以向上调用堆栈查看调用者之一是否尚未等待并执行这些语句,直到它看到等待。再次向上调用堆栈并执行语句,直到看到 await。
无法保证在未等待的异步调用之后继续执行语句的线程与原始线程相同。但是因为这个线程具有相同的 "context",所以您可以将其视为同一个线程。不需要临界区之类的。
Console.Writeline(Thread.CurrentThread.ManagedThreadId);
// async call to the text reader to read a line; don't await
var taskReadLine = myTextReader.ReadLineAsync()
// because I did not await, the following will be executed as soon as a thread is free
Console.Writeline(Thread.CurrentThread.ManagedThreadId);
...
// we need the read line; await for it
string readLine = await taskReadLine;
Console.Writeline(Thread.CurrentThread.ManagedThreadId);
ProcessReadLine(readLine);
无法保证执行 DoSomething 的线程与用于调用 ReadLineAsync 的线程相同。如果您在一个简单的测试程序中执行代码,您获得多个线程 ID 的可能性很高。
在等待结果之前,您的代码不应依赖于要执行的异步函数中的任何语句:
async Task<int> DoIt()
{
this.X = 4;
await DoSomethingElseAsync(this.X);
return 5;
}
async Task CallDoItAsync()
{
this.X = 0;
var taskDoIt = DoIt();
// you didn't await, it is not guaranteed that this.X already changed to 4
...
int i = await taskDoIt();
// now you can be certain that at some moment 4 had been assigned to this.X
创建任务对象不会创建线程。创建线程的开销相当大。因此,您的进程有一个线程池,其中包含多个线程。空闲的线程被放入池中,并可根据请求执行其他操作。一旦您的进程需要一个线程,它就会从线程池中获取一个可用线程并将其调度为 运行.
我不确定如果池中没有可用线程会发生什么情况。我猜你的函数只需要等待一个可用的线程。
您可以使用静态线程池访问线程池class。
ThreadPool.GetMaxThreads (out int workerThreads, out int completionPortThreads);
++workerThreads;
++completionPortThreads;
bool success = ThreadPool.SetMaxThreads (workerThreads, completionPortThreads);
更换线程池要非常小心!
有人说 async-await 只是为了保持 UI 响应有用,但下面显示它也可以提高处理速度。
非异步:
void CopyFile(FileInfo infile, FileInfo outFile)
{
using(var textReader = inFile.OpenText())
{
using (var textWriter = outFile.CreateText())
{
// Read a line. Wait until line read
var line = textReader.ReadLine();
while (line != null)
{
// Write the line. Wait until line written
textWrite.WriteLine(line);
// Read the next line. Wait until line read
line = textReader.ReadLine();
}
}
}
}
你看到所有的等待。幸运的是 TextReader 和 TextWriter 做缓冲数据,否则我们真的不得不等到数据写入后才能读取下一行
async Task CopyFileAsync(FileInfo infile, FileInfo outFile)
{
using(var textReader = inFile.OpenText())
{
using (var textWriter = outFile.CreateText())
{
// Read a line. Wait until line read
var line = await textReader.ReadLineAsync();
while (line != null)
{
// Write the line. Don't wait until line written
var writeTask = textWrite.WriteLineAsync(line);
// While the line is being written, I'm free to read the next line.
line = textReader.ReadLine();
// await until the previous line has been written:
await writeTask;
}
}
}
}
正在写入一行时,我们已经尝试读取下一行。这样可以提高处理速度。
If it's like that there's no time gain when using await async since no extra thread is used.
这是正确的。 async
和 await
本身并不直接使用线程。它们的目的是释放调用线程。
What i knew so far is that only Task.Run() create a new thread. Is this also true for Task.WhenAll() or Task.WhenAny() ?
否; Task.WhenAll
和 Task.WhenAny
都不直接使用任何线程。
When creating the getStringTask Task, another thread will copy the current context and start executing the GetStringAsync method.
没有。 GetStringAsync
在当前线程上同步调用,就像任何其他方法一样。同样,它 return 是一项未完成的任务。
When awaiting getStringTask, we will see if the other thread has completed his task, if not the control will be back the caller of AccessTheWebAsync() method until the other thread complets it's task to resume the control.
关闭,只是没有其他线程。 await getStringTask
将检查 任务 是否完成;如果不是,那么它将 return 来自 AccessTheWebAsync
.
Can someone exmplain what exactly happening when awaiting a Task ?
我建议阅读我的 async
intro 了解更多详情。
I've red various articles about async await and i'm trying to understand the await async in depth.
崇高的追求。
My problem is that i found out that awaiting an asyncronous method doesn't creat a new thread, it rather just make the UI responsive.
正确。认识到await
意味着异步等待是非常重要的。它 而不是 意味着 "make this operation asynchronous"。这意味着:
- 这个操作已经是异步的。
- 如果操作完成,获取其结果
- 如果操作未完成,return 给调用者并将此工作流的剩余部分分配为未完成操作的继续。
- 当未完成的操作完成时,它将安排继续执行。
If it's like that there's no time gain when using await async since no extra thread is used.
这是不正确的。你没有正确考虑时间赢。
想象一下这个场景。
- 想象一个没有 ATM 的世界。我在那个世界长大。那是一段奇怪的时光。所以在银行通常会有人排队等候存款或取款。
- 假设这家银行只有一名出纳员。
- 现在想象一下,银行只接受和发放单张美元钞票。
假设有三个人在排队,他们每人要十美元。你排在队伍的最后,你只想要一美元。这里有两个算法:
- 给排在第一位的人一美元。
- [这样做十次]
- 给排在第二位的人一美元。
- [这样做十次]
- 给排在第三位的人一美元。
- [这样做十次]
- 给你美元。
每个人要等多久才能拿到所有的钱?
- 第一个人等待 10 个时间单位
- 人二等20
- 人三等30
- 你等31.
这是一个同步算法。异步算法是:
- 给排在第一位的人一美元。
- 给排在第二位的人一美元。
- 给排在第三位的人一美元。
- 给你美元。
- 给排在第一位的人一美元。
- ...
这是一个异步解决方案。现在大家要等多久?
- 每人拿到 10 美元等 30 美元左右。
- 你等了 4 个单位。
大型作业的平均吞吐量较低,但小型作业的平均吞吐量高得多。 那是胜利。此外,每个人的time-to-first-dollar在异步工作流中较低,即使时间最后一美元 对于大型工作来说更高。另外,异步系统公平;每个作业大约等待(作业大小)x(作业数量)。在同步系统中,有些作业几乎没有等待时间,有些作业等待时间很长。
另一个胜利是:出纳员很贵;该系统雇用了一名出纳员,并为小型工作提供了良好的吞吐量。如您所述,要在同步系统中获得良好的吞吐量,您需要雇用更多出纳员,这很昂贵。
Is this also true for Task.WhenAll() or Task.WhenAny() ?
他们不创建线程。他们只是完成一堆任务,并在 all/any 个任务完成后完成。
When creating the getStringTask Task, another thread will copy the current context and start executing the GetStringAsync method.
绝对不是。该任务已经是异步的,因为它是一个 IO 任务,所以不需要线程。 IO 硬件已经是异步的。没有新员工入职
When awaiting getStringTask, we will see if the other thread has completed his task
不,没有其他线程。我们查看 IO 硬件是否已完成其任务。没有线程。
当你把一片面包放进烤面包机,然后去查看你的电子邮件时,烤面包机里没有人运行正在烤面包机。事实上,您可以启动一个异步作业,然后在它工作时离开并做其他事情,这是因为您拥有 本质上是异步的专用硬件 。网络硬件也是如此,就像烤面包机一样。 没有线程。你的烤面包机上没有小人 运行。它 运行 本身。
if not the control will be back to the caller of AccessTheWebAsync() method until the other thread completes its task to resume the control.
同样,没有其他线程。
但是控制流程是正确的。 如果任务完成,则获取任务的值。如果未完成,则在将当前工作流的剩余部分分配为任务的延续后,将 returns 控制给调用者。 当任务完成时,延续被安排为 运行.
i really don't get how no extra thread is created when awaiting a Task.
再一次,回想一下你生命中的每一次,当你因为被阻塞而停止执行某项任务时,做了一段时间的其他事情,然后当你被解除阻塞时再次开始执行第一个任务。 你有没有请工人?当然没有。然而,当吐司还在烤面包机里时,你却以某种方式成功地制作了鸡蛋。基于任务的异步只是将 real-world 工作流程放入软件中。
从来没有让我感到惊讶的是,你们今天的孩子们如何用奇怪的音乐表现得像线程一样一直存在,而且 没有其他方法可以进行多任务处理。我学会了如何在没有 有 线程的操作系统中编程。如果你想让两件事看起来同时发生,你必须建立自己的异步;它没有内置到语言或 OS 中。然而我们成功了。
协作式 single-threaded 异步对世界来说是 return ,就像我们错误地将线程引入控制流结构之前一样;一个更优雅、更简单的世界。 await 是协作式多任务系统中的暂停点。 在 pre-threading Windows 中,您会为此调用 Yield()
,而我们没有没有创建延续和闭包的语言支持;你希望状态在 yield 中持续存在,你编写了代码来做到这一点。你们都过得很轻松!
Can someone explain what exactly happening when awaiting a Task ?
正是你说的,只是没有线程。检查任务是否完成;如果它完成了,你就完成了。如果不是,则将工作流的其余部分安排为任务的延续,并且 return。这就是 await
所做的全部。
I just want to confirm something. Is it always the case that there's no thread created when awaiting a task?
我们在设计功能时担心人们会相信 "await" 会对紧随其后的 call 有所作为。 它没有。 Await 对 return 值 做了一些事情。同样,当您看到:
int foo = await FooAsync();
你应该在心里看到:
Task<int> task = FooAsync();
if (task is not already completed)
set continuation of task to go to "resume" on completion
return;
resume: // If we get here, task is completed
int foo = task.Result;
使用 await 调用方法并不是一种特殊的调用。 "await" 不会启动线程或类似的东西。它是一个运算符,对 returned 的值进行运算。
所以等待任务 不会启动线程。等待任务 (1) 检查任务是否完成,(2) 如果没有完成,则将方法的剩余部分分配为任务的继续,以及 returns。就这样。 Await 不做任何创建线程的事情。现在,也许 调用的方法 启动了一个线程;那是这是公事。这与 await 无关,因为等待直到调用 returns 之后才会发生。 被调用函数不知道其 return 值正在等待.
Let's say we await a CPU bound task that does heavy calculations. What i know so far is a I/O bound code it will be executed on low level CPU components (much lower than threads) and only use a thread briefly to notify the context about the finished Task status.
关于上面对 FooAsync 的调用,我们知道它是异步的,并且它 return 是一个任务。我们不知道如何它是异步的。那就是 FooAsync 业务的作者!但是 FooAsync 的作者可以使用三种主要技术来实现异步。正如您所注意到的,两种主要技术是:
如果任务是high-latency,因为它需要在另一台CPU的当前机器上完成长时间的计算,那么获得一个工作线程是有意义的,并且启动线程在另一个 CPU 上执行工作。工作完成后,如果任务是在 UI 线程或另一个工作线程上创建的,则相关任务可以将其继续安排到 运行 回到 UI 线程上,因为合适。
如果任务是 high-latency 因为它需要与慢速硬件(如磁盘或网络)通信,那么正如您所注意到的,没有线程。 Special-purpose 硬件异步执行任务,操作系统提供的中断处理最终负责在正确的线程上安排任务完成。
异步的第三个原因不是因为您正在管理一个 high-latency 操作,而是因为您将算法分解成小部分并将它们放在工作队列中.也许您正在制作自己的自定义调度程序,或者实现一个参与者模型系统,或者尝试进行无堆栈编程,或者其他任何事情。没有线程,没有IO,但是有异步
因此,等待 不会在工作线程上做一些事情运行。 调用启动工作线程的方法会在工作线程运行 上产生一些东西。让您调用的 方法 决定是否创建工作线程。 异步方法已经是异步的。您无需对它们执行任何操作即可 使 它们异步。 Await 不会使任何事情异步。
Await 的存在只是为了让开发人员更容易检查异步操作是否已完成,并在未完成时将当前方法的其余部分注册为继续。这就是它的用途。同样,await 不会创建异步。 Await 帮助您构建异步工作流。 await 是工作流中的一个点,在该点上,必须先完成异步任务,然后工作流才能继续。
I also know that we use Task.Run() to execute CPU bound code to look for an available thread in thread pool. Is this true ?
没错。如果你有一个同步方法,并且你知道它是 CPU 绑定的,你希望它 b异步的,而且你知道这个方法在另一个线程上运行是安全的,那么Task.Run会找一个worker线程,调度delegate在worker线程上执行,给你一个task代表异步操作。您应该只使用 (1) 非常 long-running,比如超过 30 毫秒,(2) CPU 绑定,(3) 可以安全调用另一个线程的方法。
如果您违反其中任何一条,就会发生不好的事情。如果你雇用一名工人来完成不到 30 毫秒的工作,那么,想想现实生活吧。如果你有一些计算要做,那么购买广告、面试候选人、雇用某人、让他们将三打数字加在一起然后解雇他们有意义吗? 雇用一个工作线程是昂贵的。如果雇用线程比自己完成工作更昂贵,那么雇用线程根本不会带来任何性能提升;你会让事情变得更糟。
如果您雇用一名工人来执行 IO 绑定任务,那么您所做的就是雇用一名工人在邮箱旁坐 年 并在邮件到达时大喊大叫。 这不会使邮件到达更快。它只是浪费了本可以用于其他问题的工人资源。
如果你雇用一名工人来完成一项非线程安全的任务,好吧,如果你雇用两名工人并告诉他们同时驾驶同一辆车到两个不同的地点,他们将会他们在高速公路上争夺方向盘时撞毁了汽车。