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?
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 Task
s 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 await
ed 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With