is this inefficient as you basically create a new thread for each level (asynchronously invoking an asynchronous function for each level or does it not really matter and depends only on your preferences?)
No. Asynchronous methods do not necessarily use new threads. In this case, since the main asynchronous method call is the IO binding method, there really should not be any new threads.
It:
1. necessary
For asynchronous calls, you need to bubble calls if you want the operation to be asynchronous. However, this is really preferable because it allows you to fully use asynchronous methods, including compiling them together throughout the stack.
2. negative on performance
No. As I said, 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 must do this to keep asynchronous things on the stack.
Now, you can do to improve perf. Here. If you just wrap the asynchronous method, you do not need to use language functions - just return Task :
public virtual Task Save() { return repository.Save(); }
The repository.Save() method already returns Task - you do not need to wait for it just to return it to Task . This will support a more efficient method.
You can also use "low level" asynchronous methods using ConfigureAwait to prevent the need to use a call synchronization context:
private async Task<string> Dosomething2() {
This greatly reduces the overhead associated with each await , unless you need to worry about the calling context. This is usually the best option when working on "library" code, since the "external" await will capture the user interface context. The "internal" work of the library usually does not concern the synchronization context, therefore it is better not to write this.
Finally, I would caution you against one of your examples:
private async Task<string> Dosomething3() { //other stuff ... // Potentially a bad idea! return await Task.Run(() => ""); }
If you create an async method that internally uses Task.Run to “create asynchrony” around something that is not asynchronous, you effectively complete the synchronous code in the async method. This will use the ThreadPool thread, but it can “hide” the fact that it does this, which makes the API incorrect. It is often better to leave the Task.Run call to the highest level calls and let the main methods remain synchronous if they really cannot use the asynchronous IO or some unloading tools other than Task.Run . (This is not always the case, but the code is “asynchronous”, transcoded through synchronous code through Task.Run , then returned via async / await, is often a sign of an erroneous design.)