Bilal Fazlani
Bilal Fazlani

Reputation: 6977

what happens if I await a task that is already running or ran?

There is a Task variable and lets say the task is running right now.. by executing the following line.

await _task;

I was wondering what happens when I write this code:

await _task;
await _task;

would it execute the task twice ? Or throw an exception because it has already run ?

Upvotes: 29

Views: 7625

Answers (2)

Enigmativity
Enigmativity

Reputation: 117175

I threw together this quick test:

var i = 0;
var task = Task.Run(() => { i++; Console.WriteLine(i); return i; });

var x = await task;
var y = await task;

Console.WriteLine(x);
Console.WriteLine(y);

It writes:

1
1
1

So, clearly, the task only runs once.

Upvotes: 20

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149656

would it execute the task twice ? Or throw an exception because it has already run ?

No and no. The only thing await does is call Task.GetAwaiter, it does not cause anything to run. If the task already ran to completion, it will either extract the value if it is a Task<T>, or run synchronously to the next line if it is a Task, since there is an optimization for already completed tasks.

A simple demo:

async Task Main()
{
    var foo = FooAsync();
    await foo;
    await foo;

    var bar = BarAsync();
    var firstResult = await bar;
    var secondResult = await bar;

    Console.WriteLine(firstResult);
    Console.WriteLine(secondResult);
}

public async Task FooAsync()
{
    await Task.Delay(1);
}

public async Task<int> BarAsync()
{
    await Task.Delay(1);
    return 1;
}

If you drill down to the state machine itself, you'll see this:

this.<foo>5__1 = this.<>4__this.FooAsync();
taskAwaiter = this.<foo>5__1.GetAwaiter();
if (!taskAwaiter.IsCompleted)
{
    this.<>1__state = 0;
    this.<>u__1 = taskAwaiter;
    M.<FooBar>d__0 <FooBar>d__ = this;
    this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, M.<FooBar>d__0>
                                                    (ref taskAwaiter, ref <FooBar>d__);
    return;
}

This optimization first checks the completion of the task. If the task isn't complete, it will call UnsafeOnCompleted which will register the continuation. If it is complete, it breaks the switch and goes to:

this.<>1__state = -2;
this.<>t__builder.SetResult();

Which sets the result for the underlying Task, and that way you actually get the value synchronously.

Upvotes: 33

Related Questions