Just for simplicity, I'm going to replace your example with a slightly simpler one, but it has the same significant properties:
async Task DisplayPrimeCounts() { for (int i = 0; i < 10; i++) { var value = await SomeExpensiveComputation(i); Console.WriteLine(value); } Console.WriteLine("Done!"); }
All orders are saved due to the definition of your code. Imagine going through it.
- This method is first called
- The first line of code is the for loop, so
i initialized. - The cycle check passes, so we move on to the body of the cycle.
SomeExpensiveComputation . It should return Task<T> very quickly, but the work that it will do will continue in the background.- The rest of the method is added as a continuation of the returned task; he will continue execution when this task is completed.
- After completing the task returned from
SomeExpensiveComputation , we save the result in value . value is displayed on the console.- Goto 3; note that the existing expensive operation is already completed before we go to step 4 a second time and start the next one.
As far as the C # compiler really performs step 5, it does this by creating a state machine. Basically, every time there is await , there is a label indicating where it was stopped, and at the beginning of the method (or after resuming after any continuation), it checks the current state and makes goto in the place where it stopped. It is also necessary to raise all local variables into the fields of the new class in order to preserve the state of these local variables.
Now this conversion is not actually performed in C # code, it is done in IL, but it is a kind of moral equivalent to the code that I showed above in the final machine. Note that this is not valid C # (you cannot goto in aa for like this, but this restriction does not apply to the IL code used. There are also differences between this and that C # in fact, but should give you general idea of ββwhat is 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(); } }
Please note that I ignored the task cancellation for this example, I ignored the whole concept of capturing the current synchronization context, there is a bit more error handling, etc. Do not consider this a complete implementation.
Servy
source share