Let's say that I have this code:
public async void Run()
{
TaskCompletionSource t = new TaskCompletionSource();
Prepare(t.Task);
await Task.Delay(1000);
t.SetResult();
Console.WriteLine("End");
}
public async void Prepare(Task task)
{
await Run(task, "A");
await Run(task, "B");
await Run(task, "C");
await Run(task, "D");
}
public async Task Run(Task requisite, string text)
{
await requisite;
Console.WriteLine(text);
}
What happens when the t.SetResult();
is called?
Is this multithread?
There is any guarantee of the order of the items in the Console?
If I have a List<>
, that the Run
method changes it, do I need to worry about multithread?
While you wrote a simple await
statement;
await requisite;
The C# compiler moved your method to a separate class and translated that "simple" await
into something equivalent to;
private Task requisite;
private int state = 0;
private void MoveNext()
{
switch (state)
{
case 0:
var awaiter = requisite.GetAwaiter();
if (!awaiter.IsCompleted)
{
state = 1;
awaiter.OnCompleted(MoveNext);
return;
}
goto case 1;
case 1:
// resume execution here when the task is complete
break;
}
}
As you can see, if the task was already complete, your code would continue synchronously. If the task was incomplete, a callback would be registered, to continue execution later.
The exact implementation of each awaiter can be quite different. For example Task.Yield
will return an awaiter that is never complete. Forcing the continuation to execute on the thread pool.
Most task types, and all the tasks from your example, will call each continuation method synchronously while .SetResult
is called.
Back to your specific example;
public async void Prepare(Task task)
{
await Run(task, "A");
await Run(task, "B");
await Run(task, "C");
await Run(task, "D");
}
On the first call to Run
the task is incomplete, so Run
will register a continuation Action
and return an incomplete task. The first await
in the Prepare
method will also see that the returned task is incomplete. Though the method is void
so another incomplete task is not returned.
When you resume after your delay and call t.SetResult();
, first the registered continuation to resume the Run
method will be called. When the Run
method completes, rather than returning, .SetResult
will be called on the incomplete task, causing the continuation for Prepare
to be called immediately. All of this will occur in the same thread, before t.SetResult
returns.
If you were to place a breakpoint in either Run
or Prepare
and examine the call stack, you would see how the thread stack is inverted from what you would normally expect. Each returning async
function will be there, with a call to SetResult
in the stack.