异步和等待-如何维持执行顺序? [英] Async and Await - How is order of execution maintained?
问题描述
我实际上正在阅读一些有关任务并行库以及使用async和await进行异步编程的主题. 《 Cut 5.0 in a Nutshell》一书指出,当使用await关键字等待表达式时,编译器将代码转换为以下形式:
I am actually reading some topics about the Task Parallel Library and the asynchronous programming with async and await. The book "C# 5.0 in a Nutshell" states that when awaiting an expression using the await keyword, the compiler transforms the code into something like this:
var awaiter = expression.GetAwaiter();
awaiter.OnCompleted (() =>
{
var result = awaiter.GetResult();
假设我们有这个异步功能(同样来自参考书):
Let's assume, we have this asynchronous function (also from the referred book):
async Task DisplayPrimeCounts()
{
for (int i = 0; i < 10; i++)
Console.WriteLine (await GetPrimesCountAsync (i*1000000 + 2, 1000000) +
" primes between " + (i*1000000) + " and " + ((i+1)*1000000-1));
Console.WriteLine ("Done!");
}
对"GetPrimesCountAsync"方法的调用将排队并在池化线程上执行.通常,从for循环内调用多个线程有可能引入竞争条件.
The call of the 'GetPrimesCountAsync' method will be enqueued and executed on a pooled thread. In general invoking multiple threads from within a for loop has the potential for introducing race conditions.
那么CLR如何确保请求按照发出的顺序进行处理?我怀疑编译器是否会将代码转换成上述方式,因为这会使'GetPrimesCountAsync'方法与for循环分离.
So how does the CLR ensure that the requests will be processed in the order they were made? I doubt that the compiler simply transforms the code into the above manner, since this would decouple the 'GetPrimesCountAsync' method from the for loop.
推荐答案
为简单起见,我将用稍微简单一些但具有所有相同有意义属性的示例替换您的示例:
Just for the sake of simplicity, I'm going to replace your example with one that's slightly simpler, but has all of the same meaningful properties:
async Task DisplayPrimeCounts()
{
for (int i = 0; i < 10; i++)
{
var value = await SomeExpensiveComputation(i);
Console.WriteLine(value);
}
Console.WriteLine("Done!");
}
由于代码的定义,所有顺序都得以保留.让我们想象一下逐步完成它.
The ordering is all maintained because of the definition of your code. Let's imagine stepping through it.
- 首先调用此方法
- 第一行代码是for循环,因此
i
被初始化. - 通过循环检查,因此我们进入循环的正文.
-
SomeExpensiveComputation
被调用.它应该很快返回Task<T>
,但是它所做的工作将继续在后台进行. - 该方法的其余部分作为续写添加到返回的任务中;该任务完成后它将继续执行.
- 从
SomeExpensiveComputation
返回的任务完成后,我们将结果存储在value
中. -
value
已打印到控制台. - GOTO 3;请注意,现有的昂贵操作已经完成,然后我们才能第二次执行步骤4,并开始下一个操作.
- This method is first called
- The first line of code is the for loop, so
i
is initialized. - The loop check passes, so we go to the body of the loop.
SomeExpensiveComputation
is called. It should return aTask<T>
very quickly, but the work that it'd doing will keep going on in the background.- The rest of the method is added as a continuation to the returned task; it will continue executing when that task finishes.
- After the task returned from
SomeExpensiveComputation
finishes, we store the result invalue
. value
is printed to the console.- GOTO 3; note that the existing expensive operation has already finished before we get to step 4 for the second time and start the next one.
就C#编译器实际上如何完成步骤5而言,它是通过创建状态机来完成的.基本上,每当有一个await
时,都会有一个标签指示它离开的位置,并且在方法开始时(或在任何连续激发后恢复它的位置),它会检查当前状态,并对该位置执行goto
它停在哪里.它还需要将所有局部变量提升到新类的字段中,以便保持这些局部变量的状态.
As far as how the C# compiler actually accomplishes step 5, it does so by creating a state machine. Basically every time there is an await
there's a label indicating where it left off, and at the start of the method (or after it's resumed after any continuation fires) it checks the current state, and does a goto
to the spot where it left off. It also needs to hoist all local variables into fields of a new class so that the state of those local variables is maintained.
现在这种转换实际上不是在C#代码中完成的,而是在IL中完成的,但这有点像我上面在状态机中显示的代码的士气.请注意,这不是有效的C#(您不能goto
这样进入aa for
循环,但是该限制不适用于实际使用的IL代码.这与代码之间也存在差异C#实际上可以,但是应该可以让您对这里发生的事情有一个基本的了解:
Now this transformation isn't actually done in C# code, it's done in IL, but this is sort of the morale equivalent of the code I showed above in a state machine. Note that this isn't valid C# (you cannot goto
into a a for
loop like this, but that restriction doesn't apply to the IL code that is actually used. There are also going to be differences between this and what C# actually does, but is should give you a basic idea of what's going on here:
internal class Foo
{
public int i;
public long value;
private int state = 0;
private Task<int> task;
int result0;
public Task Bar()
{
var tcs = new TaskCompletionSource<object>();
Action continuation = null;
continuation = () =>
{
try
{
if (state == 1)
{
goto state1;
}
for (i = 0; i < 10; i++)
{
Task<int> task = SomeExpensiveComputation(i);
var awaiter = task.GetAwaiter();
if (!awaiter.IsCompleted)
{
awaiter.OnCompleted(() =>
{
result0 = awaiter.GetResult();
continuation();
});
state = 1;
return;
}
else
{
result0 = awaiter.GetResult();
}
state1:
Console.WriteLine(value);
}
Console.WriteLine("Done!");
tcs.SetResult(true);
}
catch (Exception e)
{
tcs.SetException(e);
}
};
continuation();
}
}
请注意,在此示例中,我忽略了任务取消,忽略了捕获当前同步上下文的整个概念,在错误处理方面还有很多其他工作,等等.完成实施.
Note that I've ignored task cancellation for the sake of this example, I've ignored the whole concept of capturing the current synchronization context, there's a bit more going on with error handling, etc. Don't consider this a complete implementation.
这篇关于异步和等待-如何维持执行顺序?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!