what happens if I await a task that is already run

2019-03-24 09:26发布

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 ?

2条回答
欢心
2楼-- · 2019-03-24 09:48

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.

查看更多
戒情不戒烟
3楼-- · 2019-03-24 09:50

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.

查看更多
登录 后发表回答