At the End of an Async Method, Should I Return or Await

At the end of an async method, should I return or await?

You can't return the task if the method itself is declared to be async - so this won't work, for example:

async Task BarAsync()
{
return BazAsync(); // Invalid!
}

That would require a return type of Task<Task>.

If your method is just doing a small amount of work and then calling just one async method, then your first option is fine, and means there's one fewer task involved. You should be aware that any exceptions thrown within your synchronous method will be delivered synchronously though - indeed, this is how I prefer to handle argument validation.

It's also a common pattern for implementing overloading e.g. by cancellation token.

Just be aware that if you need to change to await something else, you'll need to make it an async method instead. For example:

// Version 1:
Task BarAsync()
{
// No need to gronkle yet...
return BazAsync();
}

// Oops, for version 2 I need to do some more work...
async Task BarAsync()
{
int gronkle = await GronkleAsync();
// Do something with gronkle

// Now we have to await BazAsync as we're now in an async method
await BazAsync();
}

Should I use async if I'm returning a Task and not awaiting anything

No, you should not just add async to method without await - there is even compiler warning for it.

You also should not needlessly add await inside such method as it will make compiler to generate significantly more complicated code for the method with some related performance implications.

There is no observable difference between two patterns from timing point of view - task will still run asynchronously and you still be able to await immediately or at later point in caller.

There is one difference I can think of - if you return task directly caller may use ConfigureAwait(false) and it will finish on other thread. When you await that task inside you method that method controls where code after await is executed.

Note that cost of method with single await at the end is not significantly worse than one without - so if you prefer for coding style consistent usage of async on all asynchronous methods it is likely fine except rare time critical pieces.

Should I add an async to a short method or not?

Technically there are much better explanations, but for beginners the rule of thumb should be:

Use async when

  1. a library offers you an async option, then generally it is doing something which will hugely benefit from async (HttpClient, StreamReader, etc.)
  2. if because of 1. you had to use async somewhere, then you have to use async all the way up, because every higher level method which calls into your async method is now essentially also async.
  3. if 1. and 2. don't apply, don't turn a method into async otherwise

The length of a method has nothing to do with it, actually the smaller your methods the better, because it is a sign of good coding practices.

The additional cost of async/await, even when it is literally all over your code is normally outweighed by the gains you get when you followed rule 1. and 2.

Now to answer the question if you have to decorate a method with async if you can just return the task directly:

  • Awaiting tasks: Return task or await if no code after await
  • At the end of an async method, should I return or await?

Async method - difference between await and returning a Task

The practical difference in terms of using these is: nothing. Both are 'awaitable'.

There will be a small difference in the generated code, the async version will be turned into a state-machne. Not really something to worry about.

Since these methods do nothing else it's hard to prefer one over the other. The first (simplest) one will do.

Async method waiting for end?

What your trying to achieve here is a fire and forget type mechanism. So async/await isn't really what you want. Your not wanting to await anything.

These are designed to free up threads for long running processes. Right now your returning a Task using an await and then "forgetting" it. So why return the Task at all?

Your freeing up the thread for the long running process, but your also queuing a process that ultimately does nothing (this is adding overhead that you could likely do without).

Simply doing this, would probably make more sense:

public void StartAsync() {
Task.Run(() => DoStartProcessingAsync());
}

One thing to bear in mind is that your now using a ThreadPool thread not a UI thread (depending on what is actually calling this).

Does an 'async' function immediately return resolve(undefined) unless this is explicitly overrided?

An async function will run and return a promise.

When the function gets to the end, it will resolve that promise with what would normally be the return value. (If you use await inside, then the promise won't be resolved until the async function unfreezes and reaches the end.)

The default return value for a function without a return statement is undefined, so if you don't include a return statement it will return a promise that resolves with undefined.

If you return a promise, then it will return a promise resolved with that promise which will be adopted as normal.

If you return something else, then the promise will be resolved with that value.

Return a Task instead of awaiting the inner method call

You do not need to await a method which returns a Task<T>, the code will just run asynchronously if you have the async keyword on the method. You colleague has removed that and so is running synchronously deliberately and lets the calling code implement it if they so choose.

It depends at which layer this code is being run. If it is deep it may be better to let the higher code layers choose ansynchronous execution and maintain control there.

You definitely don't need to 'write async all the way down'. The purpose of the async keyword is simply to mark a method as being able to return a Task<T> and be able to use the await keyword. The await keyword is what invokes the framework to execute the Task and provide asynchronous processing.

TL;DR: It's a choice.

I welcome anyone improving or correcting me on this as I am no guru.

Why do I need to await an async function when it is not supposedly returning a Promise?

All async functions return a promise. All of them.

That promise will eventually resolve with whatever value you return from the async function.

await only blocks execution internal to the async function. It does not block anything outside of the function. Conceptually, an async function starts to execute and as soon as it hits an await instruction, it immediately returns an unfulfilled promise (in the pending state) from the function and the outside execution world gets that promise and continues to execute.

Sometime later, the internal promise that was being awaited will resolve and then the execution of the rest of the internals of the function will continue. Eventually the internals of the function will finish and return a value. That will trigger resolving the promise that was returned from the function with that return value.

FYI, there's a lot of superfluous stuff in your load() function. You can change it from this:

async function load() {
const data = await new Promise(resolve => {
setTimeout(() => resolve([1, 2, 3]), 10);
}).then(data => data.map(i => i * 10));
console.log(`Data inside the function: ${JSON.stringify(data)}`);
return data;
}

to this:

function load() {
return new Promise(resolve => {
setTimeout(() => resolve([1, 2, 3]), 10);
}).then(data => data.map(i => i * 10));
}

Then, use it like this:

load().then(result => {
console.log(result);
});

Or, I prefer to encapsulate the manual creation of promise in their own function like this:

function delay(t, v) {
return new Promise(resolve => {
setTimeout(resolve.bind(null, v), t);
});
}

function load() {
return delay(10, [1, 2, 3]).then(data => data.map(i => i * 10));
}

And, it turns out this little delay() function is generally useful in lots of places where you want to delay a promise chain.



Thanks to everyone participating and providing me with insight. But I'm still confused how should I be using await and async.

First off, most of the time you only mark a function async if you need to use await inside the function.

Second, you most commonly use await (from within an async function) when you have multiple asynchronous operations and you want to sequence them - often because the first one provides a result that is used as input to the second. You can use await when all you have is a single asynchronous operation, but it doesn't really offer much of an advantage over a simple .then().

Here are a few examples of good reasons to use async/await:

Sequencing multiple asynchronous operations

Imagine you have getFromDatabase(), getTheUrl() and getTheContent() that are all asynchronous. If any fails, you would want to just reject the returned promise with the first error.

Here's how this looks without async/await:

function run() {
return getFromDatabase(someArg).then(key => {
return getTheURL(key);
}).then(url => {
return getTheContent(url);
}).then(content => {
// some final processing
return finalValue;
});
}

Here's how this looks with async/await:

async function run(someArg) {
let key = await getFromDatabase(someArg);
let url = await getTheURL(key);
let content = await getTheContent(url);
// some final processing
return finalValue;
}

In both cases, the function returns a promise that resolves with the finalValue so these two implementations are used the same by the caller:

run(someArg).then(finalValue => {
console.log(finalValue);
}).catch(err => {
console.log(err);
});

But, you will notice that the async/await implementation has more of a serialized, synchronous look to it and looks more like non-asynchronous code. Many find this easier to write, easier to read and easier to maintain. The more processing you have between steps, including branching, the more advantages accrue to the async/await version.

Automatically catching both rejected promises and synchronous exceptions

As I said earlier, async functions always return a promise. They also have to built-in error handling that automatically propagates errors back to that returned promise.

It goes without saying that if you manually return a promise from the async function and that promise rejects, then the promise returned from the async function will reject.

But also, if you are using await and any promise you are awaiting rejects and you don't have a .catch() on the promise and don't have a try/catch around it, then the promise the function returns will automatically reject. So, back in our previous example of this:

async function run(someArg) {
let key = await getFromDatabase(someArg);
let url = await getTheURL(key);
let content = await getTheContent(url);
// some final processing
return finalValue;
}

If any of the three promises that are being awaited reject, then the function will short circuit (stop executing any more code in the function) and reject the async returned promise. So, you get this form of error handling for free.

Then lastly, an async function also catches synchronous exceptions for you and turns them into a rejected promise.

In a normal function that returns a promise such as we had earlier:

function run() {
return getFromDatabase(someArg).then(key => {
return getTheURL(key);
}).then(url => {
return getTheContent(url);
}).then(content => {
// some final processing
return finalValue;
});
}

If getFromDatabase() throws a synchronous exception (perhaps triggered because someArg is invalid), then this overall function run() will throw synchronously. That means that for the caller to catch all possible errors from run(), they have to both surround it with a try/catch to catch the synchronous exceptions and use a .catch() to catch the rejected promise:

try {
run(someArg).then(finalValue => {
console.log(finalValue);
}).catch(err => {
console.log(err);
});
} catch(e) {
console.log(err);
}

This is messy and a bit repetitive. But, when run() is declared async, then it will NEVER throw synchronously because any synchronous exception is automatically converted to a rejected promise so you can be sure you are capturing all possible errors when it's written this way:

async function run(someArg) {
let key = await getFromDatabase(someArg);
let url = await getTheURL(key);
let content = await getTheContent(url);
// some final processing
return finalValue;
}

// will catch all possible errors from run()
run(someArg).then(finalValue => {
console.log(finalValue);
}).catch(err => {
console.log(err);
});

Should I always call all of my function with an await?

First, you would only ever use await with a function that returns a promise as await offers no usefulness if the function does not return a promise (just adding to the clutter of your code if not needed).

Second, whether you use await or not depends upon the context of both the calling function (since you HAVE to be in an async function to use await and on the flow of logic and whether it benefits from using await or not.

Places where it's pointless to use await

async function getKey(someArg) {
let key = await getFromDatabase(someArg);
return key;
}

The await here isn't doing anything useful. You're not sequencing multiple async operations and you're not doing any processing on the return value. You can accomplish the exact same code by just returning the promise directly:

async function getKey(someArg) {
return getFromDatabase(someArg);
}

And, if you know that getFromDatabase() never throws synchronously, you can even remove the async from the declaration:

function getKey(someArg) {
return getFromDatabase(someArg);
}

Let's say I'm writing a code composed of multiple functions within multiple files. If I end up using a library which returns a Promise or it's an async function, should I trace back all my function calls from the asynchronous point to the entry point of the application and add an await before all the function calls after making them async?

This is a bit too general of an ask that's hard to answer in a general case. Here are some thoughts along this general direction:

  1. Once any part of your result that you're trying to return from your function A() is asynchronous or uses any asynchronous operation to obtain, the function itself is asynchronous. In plain Javascript, you can never return an asynchronous result synchronously so your function must use an asynchronous method to return the result (promise, callback, event, etc...).

  2. Any function B() that calls your asynchronous function A() that is also trying to return a result based on what it gets from A() is now also asynchronous and also must communicate its result back using an asynchronous mechanism. This is true for a function C() that calls B() and needs to communicate back its result to the caller. So, you can say that asynchronous behavior is infectious. Until you reach some point in the call chain where you no longer need to communicate back a result, everything has to use asynchronous mechanisms to communicate the result, error and completion.

  3. There's no specific need to mark a function async unless you specifically need one of the benefits of an async function such as the ability to use await inside that function or the automatic error handling it provides. You can write functions that returning promises just fine without using async on the function declaration. So, "NO" I don't go back up the call chain making everything async. I only make a function async if there's a specific reason to do so. Usually that reason is that I want to use await inside the function, but there is also the automatic catching of synchronous exceptions that get turned into promise rejections that I described earlier. You would not generally need that with well behaved code, but it is sometimes useful with poorly behaved code orcode with an undefined behavior.

  4. await is also only used when there's a specific reason for it. I don't just automatically use it on every function that returns a promise. I've described above reasons to use it. One can still use .then() just fine for processing the result from a single function call that returns a promise. In some cases, it's just a matter of personal style whether you want to use .then() or await and there is no particular reason it has to be one one way or the other.

Or maybe I should just get into the habit of calling all my functions with an await regardless of whether they are async or not?

Absolutely NOT! First off, the last thing you want to do is to take perfectly synchronous code and unnecessarily make it asynchronous or even make it look asynchronous. Asynchronous code (even with async and await) is more complicated to write, debug, understand and maintain than synchronous code so you would never want to unnecessarily make synchronous code into asynchronous code by adding async/await into it:

For example, you would never do this:

async function random(min, max) {
let r = await Math.random();
return Math.floor((r * (max - min)) + min);
}

First off, this is a perfectly synchronous operation that can be coded like this:

function random(min, max) {
let r = Math.random();
return Math.floor((r * (max - min)) + min);
}

Second off, that first async implementation has made the function a lot hard to use as it now has an asynchronous result:

 random(1,10).then(r => {
console.log(r);
});

Instead of just the simple synchronous use:

 console.log(random(1,10));

async/await - when to return a Task vs void?


  1. Normally, you would want to return a Task. The main exception should be when you need to have a void return type (for events). If there's no reason to disallow having the caller await your task, why disallow it?

  2. async methods that return void are special in another aspect: they represent top-level async operations, and have additional rules that come into play when your task returns an exception. The easiest way is to show the difference is with an example:

static async void f()
{
await h();
}

static async Task g()
{
await h();
}

static async Task h()
{
throw new NotImplementedException();
}

private void button1_Click(object sender, EventArgs e)
{
f();
}

private void button2_Click(object sender, EventArgs e)
{
g();
}

private void button3_Click(object sender, EventArgs e)
{
GC.Collect();
}

f's exception is always "observed". An exception that leaves a top-level asynchronous method is simply treated like any other unhandled exception. g's exception is never observed. When the garbage collector comes to clean up the task, it sees that the task resulted in an exception, and nobody handled the exception. When that happens, the TaskScheduler.UnobservedTaskException handler runs. You should never let this happen. To use your example,

public static async void AsyncMethod2(int num)
{
await Task.Factory.StartNew(() => Thread.Sleep(num));
}

Yes, use async and await here, they make sure your method still works correctly if an exception is thrown.

For more information see: https://docs.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming

If every async has an await and every await is applied to an async, then where and how does this end?


However, await seems to be only applied to async methods, which in turn must have awaits inside.

Technically, await is not applied to methods. What happens is the method is called and returns something (e.g., a Task), and then the await is applied to that task.

So, the misunderstanding is here: await is applied to awaitables (e.g., tasks), not methods.

It must end somewhere, with an await being applied to a non-async method.

Yup. Any method can return a Task (or any awaitable), whether it's async or not. A method can use async to construct its task (and most of the methods we write do it this way), but it can also build one directly. Usually, low-level asynchronous methods use TaskFactory.FromAsync or TaskCompletionSource<T> to create their task that they return.



Related Topics



Leave a reply



Submit