我正在学习async/await,并对MSDN的await的解释感到困惑:" await运算符暂停执行,直到GetByteArrayAsync方法的工作完成.同时,控制权返回给GetPageSizeAsync的调用者 "
我不明白的是,"返回"是什么意思?起初,我相信当一个线程(比方说,UI线程)到达"await"关键字时,系统将创建一个新线程(或从threadPool获取一个线程)来执行其余的代码,并且UI线程可以返回调用方法并执行其余的操作.
但现在我知道"等待"永远不会创造线程.
我写了一个演示:
class Program
{
static void Main(string[] args)
{
new Test().M1();
Console.WriteLine("STEP:8");
Console.Read();
}
}
class Test
{
public async void M1()
{
Console.WriteLine("STEP:1");
var t = M2();
Console.WriteLine("STEP:3");
await t;
}
public async Task<string> M2()
{
Console.WriteLine("STEP:2");
string rs = await M3();//when the thread reaches here,why don't it return to M1 and execute the STEP:3 ??
Console.WriteLine("STEP:7");
return rs;
}
public async Task<string> M3()
{
Console.WriteLine("STEP:4");
var rs = Task.Run<string>(() => {
Thread.Sleep(3000);//simulate some work that takes 3 seconds
Console.WriteLine("STEP:6");
return "foo";
});
Console.WriteLine("STEP:5");
return await rs;
}
}
Run Code Online (Sandbox Code Playgroud)
这个演示打印一些代表执行流程的标签,我认为它会是
步骤1
第2步
步骤:3
第4步
STEP:5
STEP:6
STEP:7
STEP:8
(按顺序从1到8),
但实际结果如下: 图1
步骤1
第2步
第4步
STEP:5
步骤:3
STEP:8
STEP:6
STEP:7
如果像MSDN的解释一样,控制权返回M1并打印"STEP 3",绝对是错的,那究竟发生了什么?
提前致谢
只是为了让它不受影响,我们无法保证在任何特定的await时刻我们都会做任何事情,除了继续执行其余的代码.但是,在MSDN中的示例和您自己的示例中,我们将始终等待每个await点.
所以,我们达到了某种程度,我们await z已经决定等待.这意味着a)z尚未完成(无论z完成的意义不是我们在此时关心的事情)和b)我们现在没有任何有用的工作要做.
希望从上面可以看出为什么"创建一个新线程"或类似的东西是没有必要的,因为正如我刚才所说,没有什么有用的工作要做.
在我们从我们的方法返回控制之前,我们将排队继续.该async机器能够表达" 当 Z有完成,安排其余这种方法从我们继续await点".
在这里,诸如同步上下文之类的东西ConfigureAwait变得相关.我们正在运行的线程(以及我们即将放弃控制权)可能在某种程度上是"特殊的".它可能是UI线程.它目前可能拥有对某些资源的独占访问权(想想ASP.Net预核心请求/响应/会话对象).
如果是这样的话,希望提供特殊功能的系统安装了一个同步上下文,并且通过它我们也能够获得" 当我们恢复执行这个方法时,我们需要具有我们之前所拥有的相同特殊情况" .因此,例如,我们可以在UI线程上恢复运行我们的方法.
默认情况下,如果没有同步上下文,将找到线程池线程来运行我们的延续.
请注意,如果某个方法包含await需要等待的多个s,那么在第一次等待之后,我们将作为链式延续运行.我们的原始来电者在我们第一次await编辑时得到了他们的背景.
在你的样本中,我们有三个await点,所有三个点都会等待并让我们放弃对我们的调用者的控制(我们await在单一方法中没有多个s来担心).所有这些等待都是在一天结束时等待它Thread.Sleep完成(现代async代码中的不良形式应该使用TaskDelay).因此Console.WriteLine,await 在该方法之后出现的任何s 都将被延迟.
然而,M1是async void,最好的东西的事件处理程序之外避免.async void方法是有问题的,因为它们无法确定何时完成.
Main调用M1它打印1,然后调用M2.M2打印2,然后打电话M3.M3打印4,创建一个新的Task,打印5,然后它才能放弃控制.在这一点上,它创建了一个TaskCompletionSource代表其最终完成的东西,从中获取Task并返回它.
请注意,只有当我们进入的M3 回调调用时才会await进入M2.我们也必须在这里等,所以我们几乎一样,M3并返回一个Task.
现在M1终于Task可以await了.但在此之前,它会打印出来3.它返回Main现在打印的控件8.
Eons之后,Thread.Sleepin rsin M3完成运行,我们打印6.M3标志着它返回Task完整,没有更多的工作要做,所以退出没有进一步的延续安排.
M2现在可以恢复await和打印6.完成后,它Task已经完成,M1最终可以恢复和打印7.
M1没有Task标记为完整,因为它是async void.
| 归档时间: |
|
| 查看次数: |
329 次 |
| 最近记录: |