简体   繁体   中英

await Task vs await Task.Run(voidMethod)

I'm confused why the output of these 2 programs differs:

private async void button1_Click(object sender, EventArgs e)
{
    for (int i = 0; i < 33; i++)
    {
        await LongProcess();
    }
}

private async Task LongProcess()
{
    await Task.Delay(1000);
    progressBar1.Value += 3;
}

and

private async void button1_Click(object sender, EventArgs e)
{
    for (int i = 0; i < 33; i++)
    {
        await Task.Run(() => LongProcess());
    }
}

private async void LongProcess()
{
    await Task.Delay(1000);
    progressBar1.Value += 3;
}

I realize the first example returning Task is more correct, but I don't understand why wrapping the void function in a Task.Run doesn't produce the same output? The first function does what I expect, updates the progress bar every 1 second. The second code attempts to update the progress bar all at once, causing problems attempting to update the same UI element from multiple threads.

My assumption was since the buttonClick method awaits the long process to complete, both sets of code should not allow the progressBar1 update to happen until the previous process has completed. Why does the second set of code allow it to happen all at once?

This isn't doing what you think it is:

await Task.Run(() => LongProcess());

The code is awaiting Task.Run() , but nothing within that task is awaiting LongProcess() . So Task.Run() returns immediately in this case.

This is actually an interesting illustration of a failure to be "async all the way down", because the inline function is essentially hiding the fact that it's async. And in fact the compiler should be warning you that nothing is awaiting LongProcess() and that it would return immediately.

Contrast it with this:

await Task.Run(async () => await LongProcess());

Edit: I just noticed why the compiler probably isn't warning you. Because of this:

async void

Never, ever, ever do this :) (Well, ok, there's one valid reason to do this. And I'm sure it haunts the C# team to this day that they had to support it just for that one reason. But unless you encounter that one reason, don't do it.)

Always return a Task for async methods so that the method can be awaited.

In the first program, LongProcess returns a Task , and Task.Run is wrapping it -- basically just launching it in the default scheduler rather than whatever context you're currently on.

You'll notice that Task.Run has overloads specifically to do this wrapping, and is not returning a Task<Task> .

In the second program, LongProcess is an async void method, which means Task.Run has nothing to wrap and will complete more or less immediately, and before the work is guaranteed to be done.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM