[英]Confused at control flow of async/await of c#
I'm learning about async/await ,and get confused on the explaination of the await of MSDN :" The await operator suspends execution until the work of the GetByteArrayAsync method is complete. In the meantime, control is returned to the caller of GetPageSizeAsync " 我正在学习async / await,并对MSDN的await的解释感到困惑:“ await运算符暂停执行,直到GetByteArrayAsync方法的工作完成。同时,控制权返回给GetPageSizeAsync的调用者 ”
What i don't understand is,what does it mean "returned"? 我不明白的是,“返回”是什么意思? At first,i believed when a thread(let's say, the UI thread) reaches the "await" keyword, system will create a new thread(or get a thread from threadPool) to execute the rest of codes,and UI thread can return to the caller method and execute the rest.
起初,我相信当一个线程(比方说,UI线程)到达“await”关键字时,系统将创建一个新线程(或从threadPool获取一个线程)来执行其余的代码,并且UI线程可以返回调用方法并执行其余的操作。
But now i know that "await" will never create thread. 但现在我知道“等待”永远不会创造线程。
I write a demo: 我写了一个演示:
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;
}
}
this demo prints some labels representing the execute flow,which i thought it would be 这个演示打印一些代表执行流程的标签,我认为它会是
STEP:1 第1步
STEP:2 第2步
STEP:3 步骤:3
STEP:4 第四步
STEP:5 STEP:5
STEP:6 STEP:6
STEP:7 STEP:7
STEP:8 STEP:8
(from 1 to 8 in order), (按顺序从1到8),
but the actual result is : Figure 1 但实际结果如下: 图1
STEP:1 第1步
STEP:2 第2步
STEP:4 第四步
STEP:5 STEP:5
STEP:3 步骤:3
STEP:8 STEP:8
STEP:6 STEP:6
STEP:7 STEP:7
If like MSDN's explaination, control is returned to M1 and print the "STEP 3", absolutely i'm wrong,So what's going on exactly? 如果像MSDN的解释一样,控制权返回M1并打印“STEP 3”,绝对是错的,那究竟发生了什么?
Thanks in advance 提前致谢
Just to get it out of the way, there's no guarantee that, at any particular await
point we're going to do anything beyond carrying on with the rest of the code. 只是为了让它不受影响,我们无法保证,在任何特定的
await
点,除了继续执行其余的代码之外,我们将做任何事情 。 However, in the sample in MSDN and your own example, we will always wait at each of the await
points. 但是,在MSDN中的示例和您自己的示例中,我们将始终等待每个
await
点。
So, we reach some point where we have await z
and we've decided that we're going to wait. 所以,我们达到某种程度,我们
await z
,我们决定等待。 This means a) That z
hasn't Completed yet (whatever it means for z
to be complete isn't something we care about at this moment in time) and b) That we have no useful work to do ourselves, at the moment. 这意味着a)
z
还没有完成(无论z
是完整的意味着什么都不是我们在这个时刻所关心的事情)和b)我们现在没有任何有用的工作要做。
Hopefully, from the above, you can see why "creating a new thread" or anything like it isn't necessary, because like I just said, there's no useful work to do. 希望从上面可以看出为什么“创建一个新线程”或类似的东西是没有必要的,因为正如我刚才所说,没有什么有用的工作要做。
Before we return control from our method though, we're going to enqueue a continuation . 之前我们从我们的方法,虽然返回控制,我们要排队的延续 。 The
async
machinery is able to express " when z has completed, arrange for the rest of this method to continue from our await
point". async
机器能够表达“ 当 z完成时,安排该方法的其余部分从我们的await
点继续”。
Here, things like synchronization contexts and ConfigureAwait
become relevant. 在这里,同步上下文和
ConfigureAwait
类的东西变得相关。 The thread that we're running on (and that we're about to relinquish control of) may be "special" in some way. 我们正在运行的线程(以及我们即将放弃控制权)可能在某种程度上是“特殊的”。 It may be the UI thread.
它可能是UI线程。 It may currently have exclusive access to some resources (think ASP.Net pre-Core Request/Response/Session objects).
它目前可能拥有对某些资源的独占访问权(想想ASP.Net预核心请求/响应/会话对象)。
If that's so, hopefully the system providing the special-ness has installed a synchronization context, and it's via that that we also are able to obtain " when we resume execution of this method, we need to have the same special circumstances we previously had". 如果是这样的话,希望提供特殊功能的系统安装了一个同步上下文,并且通过它我们也能够获得“ 当我们恢复执行这个方法时,我们需要具有我们之前所拥有的相同特殊情况” 。 So eg we're able to resume running our method back on the UI thread .
因此,例如,我们可以在UI线程上恢复运行我们的方法。
By default, without a synchronization context, a thread pool thread will be found to run our continuation. 默认情况下,如果没有同步上下文,将找到线程池线程来运行我们的延续。
Note that, if a method contains multiple await
s that require waiting, after that first wait, we'll be running as a chained continuation. 注意,如果一个方法包含需要等待的多个
await
,那么在第一次等待之后,我们将作为链式延续运行。 Our original caller got their context back the first time we await
ed. 我们的原始来电者在我们第一次
await
时获得了他们的背景。
In your sample, we have three await
points and all three of them will wait and cause us to relinquish control back to our caller (and we don't have multiple await
s in a single method to worry about either). 在你的样本中,我们有三个
await
点,并且它们中的所有三个将等待并使我们放弃对我们的调用者的控制(并且我们没有多个await
s在单个方法中担心)。 All of these awaits are, at the end of the day, waiting for that Thread.Sleep
to finish (poor form in modern async
code which should use TaskDelay
instead). 所有这些等待都是在一天结束时,等待
Thread.Sleep
完成(现代async
代码中的差形式应该使用TaskDelay
)。 So any Console.WriteLine
s appearing after an await
in that method will be delayed. 因此, 在该方法中
await
之后出现的任何Console.WriteLine
都将被延迟。
However, M1
is async void
, something best avoided outside of event handlers. 但是,
M1
是async void
,在事件处理程序之外最好避免使用。 async void
methods are problematic because they offer no means of determining when they've completed. async void
方法存在问题,因为它们无法确定何时完成。
Main
calls M1
which prints 1
and then calls M2
. Main
调用M1
打印1
然后调用M2
。 M2
prints 2
and then calls M3
. M2
打印2
然后调用M3
。 M3
prints 4
, creates a new Task
, prints 5
and only then can it relinquish control. M3
打印4
,创建一个新的Task
,打印5
,然后才可以将其放弃控制权。 It's at this point that it creates a TaskCompletionSource
that will represent its eventual completion, obtain the Task
from it, and return that. 在这一点上,它创建了一个
TaskCompletionSource
,它将代表它的最终完成,从中获取Task
,并返回它。
Notice that it's only when the call to M3
returns that we hit the await
in M2
. 请注意,只有当
M3
的调用返回时 ,我们才会在M2
进行await
。 We also have to wait here so we do almost the same as M3
and return a Task
. 我们还必须在这里等待,这样我们几乎和
M3
一样,并返回一个Task
。
Now M1
finally has a Task
that it can await
on. 现在
M1
终于有了一个可以await
的Task
。 But before it does that, it prints 3
. 但在此之前,它打印
3
。 It returns control to Main
which now prints 8
. 它将控制返回到
Main
,现在打印8
。
Eons later, the Thread.Sleep
in rs
in M3
finishes running, and we print 6
. 亿万年后,
Thread.Sleep
在rs
在M3
运行完毕,我们打印6
。 M3
marks its returned Task
as complete, has no more work to do, and so exits with no further continuations arranged. M3
其返回的Task
标记为完成,没有更多的工作要做,因此退出时不再安排进一步的延续。
M2
can now resume and its await
and print 6
. M2
现在可以恢复, await
并打印6
。 Having done that, its Task
is complete and M1
can finally resume and print 7
. 完成后,其
Task
完成, M1
最终可以恢复和打印7
。
M1
doesn't have a Task
to mark as complete because it was async void
. M1
没有Task
标记为完整,因为它是async void
。
It's easiest to see when you look at what this code is actually doing under the hood. 当你看到这段代码实际上在做什么时,最容易看到。 Here's an approximate translation of what that code is doing:
这是代码正在做的大致翻译:
public void M1()
{
Console.WriteLine("STEP:1");
var t = M2();
Console.WriteLine("STEP:3");
t.ContinueWith(_ =>
{
//do nothing
});
}
public Task<string> M2()
{
Console.WriteLine("STEP:2");
Task<string> task = M3();
return task.ContinueWith(t =>
{
Console.WriteLine("STEP:7");
return t.Result;
});
}
public 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 rs.ContinueWith(t => t.Result);
}
Note that, for the sake of brevity, I've not done proper error handling here. 请注意,为了简洁起见,我没有在这里进行适当的错误处理。 One of the biggest benefits to using
await
is that it handles errors in the way you generally want them to, and using the tools I have here...doesn't. 使用
await
的最大好处之一是它以你通常想要的方式处理错误,并且使用我在这里的工具......没有。 This code also doesn't schedule the continuations to use the current synchronization context, something that's irrelevant to this situation, but is a useful feature. 此代码也没有安排继续使用当前同步上下文,这与这种情况无关,但是是一个有用的功能。
At this point it hopefully is more clear as to what's going on. 在这一点上,希望更清楚的是发生了什么。 For starters,
M1
and M3
really have no business being async
in the first place, as they never doing anything meaningful in their continuations. 对于初学者来说,
M1
和M3
确实没有业务首先是async
,因为他们从来没有做过任何有意义的延续。 They should really just not be async
and to return the tasks they get from calling other methods. 它们实际上应该不是
async
并且返回它们从调用其他方法获得的任务。 M2
is the only method that actually does anything useful in any of the continuations it creates. M2
是唯一在其创建的任何延续中实际执行任何有用的方法。
It also makes it clearer as to the order of operations. 它还使操作顺序更加清晰。 We call
M1
, which calls M2
, which calls M3
, which calls Task.Run
, then M3
returns a task that's identical to what Task.Run
returns and then returns itself, then M2
adds its continuation and returns, then M3
executes the next printline, then adds its continuation (which does nothing when it eventually runs), then returns. 我们调用
M1
,调用M2
,调用M3
,调用Task.Run
,然后M3
返回一个与Task.Run
返回相同的任务,然后返回自身,然后M2
添加其继续并返回,然后M3
执行下一个printline ,然后添加它的延续(当它最终运行时什么都不做),然后返回。 Then at some later point in time the Task.Run
finishes, and all of the continuations run, bottom up (because each is a continuation of the other). 然后在稍后的某个时间点,
Task.Run
完成,并且所有的延续都是自下而上的(因为每个都是另一个的延续)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.