Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between these awaitable methods?

I'm looking at some asynchronous programming in C# and was wondering what the difference would be between these functions that does the exact same thing and are all awaitable.

public Task<Bar> GetBar(string fooId)
{
    return Task.Run(() =>
    {
        var fooService = new FooService();
        var bar = fooService.GetBar(fooId);
        return bar;
    });
}

public Task<Bar> GetBar(string fooId)
{
    var fooService = new FooService();
    var bar = fooService.GetBar(fooId);
    return Task.FromResult(bar)
}

public async Task<Bar> GetBar(string fooId)
{
    return await Task.Run(() =>
    {
        var fooService = new FooService();
        var bar = fooService.GetBar(fooId);
        return bar;
    });
}

My guess is that the first is the correct way to do things, and the code isn't executed until you try to get the result from the returned Task.

In the second, the code is executed on call and the result is stored in the returned task.

And the third is kind of like the second? The code is executed on call and the result of the Task.Run is return? Which in that case this function would be kind of stupid?

Am I right or am I way off?

like image 232
Gralov Avatar asked Feb 22 '16 10:02

Gralov


1 Answers

None of these method implementations make sense. All you're doing is pushing out blocking work onto the thread pool (or worse, running it synchronously and wrapping the result in a Task<Bar> instance). What you should do instead is expose the synchronous API, and let the callers decide how to call it. Whether they want to use Task.Run or not is then up to them.

Having said that, here are the differences:

#1

The first variant (which returns a Task<Bar> created via Task.Run directly) is the "purest" even if it doesn't make much sense from an API perspective. You're allowing Task.Run to schedule the given work on the thread pool and return the Task<Bar> representing the completion of the async operation to the caller.

#2

The second method (which utilises Task.FromResult) is not asynchronous. It executes synchronously, just like a regular method call. The result is simply wrapped in a completed Task<Bar> instance.

#3

This is a more convoluted version of the first. You are achieving an outcome similar to what #1 does, but with an extra, unnecessary, and even somewhat dangerous await. This one is worth looking at in more detail.

async/await is great for chaining async operations via combining multiple Tasks representing asynchronous work into a single unit (Task). It helps you get things happening in the right order, gives you rich control flow between your async operations and ensures that things happen on the right thread.

None of the above, however, is of any benefit in your scenario, because you only have one Task. Therefore, there is no need to make the compiler generate a state machine for you just to accomplish what Task.Run already does.

A poorly designed async method can also be dangerous. By not using ConfigureAwait(false) on your awaited Task you are inadvertently introducing a SynchronizationContext capture, killing performance and introducing a deadlock risk for no benefit.

If your caller decides to block on your Task<Bar> in an environment which has a SynchronizationContext (i.e. Win Forms, WPF and possibly ASP.NET) via GetBar(fooId).Wait() or GetBar(fooId).Result, they will get a deadlock for the reasons discussed here.

like image 149
Kirill Shlenskiy Avatar answered Sep 19 '22 04:09

Kirill Shlenskiy