If you are using async/await at a lower level in your architecture, is it necessary to \"bubble up\" the async/await calls all the way up, is it inefficient since you are b
is it inefficient since you are basically creating a new thread for each layer (asynchronously calling an asynchronous function for each layer, or does it not really matter and is just dependent on your preference?
No. Asynchronous methods do not necessarily use new threads. In this case, since the underlying asynchronous method call is an IO bound method, there really should be no new threads created.
Is this:
1. necessary
It is necessary to "bubble" up the async calls if you want to keep the operation asynchronous. This really is preferred, however, as it allows you to fully take advantage of the asynchronous methods, including composing them together across the entire stack.
2. negative on performance
No. As I mentioned, this does not create new threads. There is some overhead, but much of this can be minimized (see below).
3. just a matter of preference
Not if you want to keep this asynchronous. You need to do this to keep things asynchronous across the stack.
Now, there are some things you can do to improve perf. here. If you're just wrapping an asynchronous method, you don't need to use the language features - just return the Task:
public virtual Task Save()
{
return repository.Save();
}
The repository.Save() method already returns a Task - you don't need to await it just to wrap it back in a Task. This will keep the method somewhat more efficient.
You can also have your "low level" asynchronous methods use ConfigureAwait to prevent them from needing the calling synchronization context:
private async Task Dosomething2()
{
//other stuff
...
return await Dosomething3().ConfigureAwait(false);
}
This dramatically reduces the overhead involved in each await if you don't need to worry about the calling context. This is typically the best option when working on "library" code, since the "outer" await will capture the UI's context. The "inner" workings of the library don't typically care about synchronization context, so it's best to not capture that.
Finally, I'd caution against one of your examples:
private async Task Dosomething3()
{
//other stuff
...
// Potentially a bad idea!
return await Task.Run(() => "");
}
If you're making an async method which, internally, is using Task.Run to "create asynchrony" around something that's not itself asynchronous, you're effectively wrapping up synchronous code into an async method. This will use a ThreadPool thread, but can "hide" the fact that it's doing so, effectively making the API misleading. It's often better to leave the call to Task.Run for your highest level calls, and let the underlying methods stay synchronous unless they are truly able to take advantage of asynchronous IO or some means of unloading other than Task.Run. (This isn't always true, but "async" code wrapped over synchronous code via Task.Run, then returned via async/await is often a sign of a flawed design.)