Are There Performance Concerns with 'Return Await'

Are there performance concerns with `return await`?

No, there isn't any performance problem. It's just an unnecessary extra operation. It might take a bit longer to execute, but should be hardly noticeable. It's akin to return x+0 instead of return x for an integer x. Or rather, exactly equivalent to the pointless .then(x => x).

It doesn't do actual harm, but I'd consider it bad style and a sign that the author does not fully compre­hend promises and async/await.

However, there's one case where it make an important difference:

try {

return await …;
} …

await does throw on rejections, and in any case awaits the promise resolution before catch or finally handlers are executed. A plain return would have ignored that.

async and await: are they bad?

Is there anything silly with this method? Note that when we converted
all method to non-async methods we got a heaps better performance.

I can see at least two things going wrong here:

public static async Task<T> GetApiResponse<T>(object parameters, string action, CancellationToken ctk)
{
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri(BaseApiAddress);

var formatter = new JsonMediaTypeFormatter();

return
await
httpClient.PostAsJsonAsync(action, parameters, ctk)
.ContinueWith(x => x.Result.Content
.ReadAsAsync<T>(new[] { formatter }).Result, ctk);
}
}

Firstly, the lambda you're passing to ContinueWith is blocking:

x => x.Result.Content.ReadAsAsync<T>(new[] { formatter }).Result

This is equivalent to:

x => { 
var task = x.Result.Content.ReadAsAsync<T>(new[] { formatter });
task.Wait();
return task.Result;
};

Thus, you're blocking a pool thread on which the lambda is happened to be executed. This effectively kills the advantage of the naturally asynchronous ReadAsAsync API and reduces the scalability of your web app. Watch out for other places like this in your code.

Secondly, an ASP.NET request is handled by a server thread with a special synchronization context installed on it, AspNetSynchronizationContext. When you use await for continuation, the continuation callback will be posted to the same synchronization context, the compiler-generated code will take care of this. OTOH, when you use ContinueWith, this doesn't happen automatically.

Thus, you need to explicitly provide the correct task scheduler, remove the blocking .Result (this will return a task) and Unwrap the nested task:

return
await
httpClient.PostAsJsonAsync(action, parameters, ctk).ContinueWith(
x => x.Result.Content.ReadAsAsync<T>(new[] { formatter }),
ctk,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext()).Unwrap();

That said, you really don't need such added complexity of ContinueWith here:

var x = await httpClient.PostAsJsonAsync(action, parameters, ctk);
return await x.Content.ReadAsAsync<T>(new[] { formatter });

The following article by Stephen Toub is highly relevant:

"Async Performance: Understanding the Costs of Async and Await".

If I have to call an async method in a sync context, where using await
is not possible, what is the best way of doing it?

You almost never should need to mix await and ContinueWith, you should stick with await. Basically, if you use async, it's got to be async "all the way".

For the server-side ASP.NET MVC / Web API execution environment, it simply means the controller method should be async and return a Task or Task<>, check this. ASP.NET keeps track of pending tasks for a given HTTP request. The request is not getting completed until all tasks have been completed.

If you really need to call an async method from a synchronous method in ASP.NET, you can use AsyncManager like this to register a pending task. For classic ASP.NET, you can use PageAsyncTask.

At worst case, you'd call task.Wait() and block, because otherwise your task might continue outside the boundaries of that particular HTTP request.

For client side UI apps, some different scenarios are possible for calling an async method from synchronous method. For example, you can use ContinueWith(action, TaskScheduler.FromCurrentSynchronizationContext()) and fire an completion event from action (like this).

Performance impact of using async await when its not necessary

In the first method compiler will generate "state machine" code around it and execution will be returned to the line return await service.GetAsync(); after task will be completed. Consider example below:

public async Task<Something> GetSomethingAsync()
{
var somethingService = new SomethingService();

// Here execution returns to the caller and returned back only when Task is completed.
Something value = await service.GetAsync();

DoSomething();

return value;
}

The line DoSomething(); will be executed only after service.GetAsync task is completed.

Second approach simply starts execution of service.GetAsync and return correspondent Task to the caller without waiting for completion.

public Task<Something> GetSomethingAsync()
{
var somethingService = new SomethingService();

Task<Something> valueTask = service.GetAsync();

DoSomething();

return valueTask;
}

So in the example above DoSomething() will be executed straight after line Task<Something> valueTask = service.GetAsync(); without waiting for completion of task.

Executing async method on the another thread depend on the method itself.

If method execute IO operation, then another thread will be only waste of the thread, which do nothing, only waiting for response. On my opinion async - await are perfect approach for IO operations.

If method GetAsync contains for example Task.Run then execution goes to the another thread fetched from thread pool.

Below is short example, not a good one, but it show the logic a tried to explain:

static async Task GetAsync()
{
for(int i = 0; i < 10; i++)
{
Console.WriteLine($"Iterate GetAsync: {i}");
await Task.Delay(500);
}
}

static Task GetSomethingAsync() => GetAsync();

static void Main(string[] args)
{
Task gettingSomethingTask = GetSomethingAsync();

Console.WriteLine("GetAsync Task returned");

Console.WriteLine("Start sleeping");
Thread.Sleep(3000);
Console.WriteLine("End sleeping");

Console.WriteLine("Before Task awaiting");
gettingSomethingTask.Wait();
Console.WriteLine("After Task awaited");

Console.ReadLine();
}

And output will be next:

Iterate GetAsync: 0
GetAsync Task returned
Start sleeping
Iterate GetAsync: 1
Iterate GetAsync: 2
Iterate GetAsync: 3
Iterate GetAsync: 4
Iterate GetAsync: 5
End sleeping
Before Task awaiting
Iterate GetAsync: 6
Iterate GetAsync: 7
Iterate GetAsync: 8
Iterate GetAsync: 9
After Task awaited

As you can see executing of GetAsync starts straight after calling it.

If GetSomethingAsync() will be changed to the:

static Task GetSomethingAsync() => new Task(async () => await GetAsync());

Where GetAsync wrapped inside another Task, then GetAsync() will not be executed at all and output will be:

GetAsync Task returned
Start sleeping
End sleeping
Before Task awaiting
After Task awaited

Of course you will need to remove line gettingSomethingTask.Wait();, because then application just wait for task which not even started.

Async / await vs then which is the best for performance?

From a performance point of view, await is just an internal version of .then() (doing basically the same thing). The reason to choose one over the other doesn't really have to do with performance, but has to do with desired coding style or coding convenience. Certainly, the interpreter has a few more opportunities to optimize things internally with await, but its unlikely that should be how you decide which to use. If all else was equal, I would choose await for the reason cited above. But, I'd first choose which made the code simpler to write and understand and maintain and test.

Used properly, await can often save you a bunch of lines of code making your code simpler to read, test and maintain. That's why it was invented.

There's no meaningful difference between the two versions of your code. Both achieve the same result when the axios call is successful or has an error.

Where await could make more of a convenience difference is if you had multiple successive asynchronous calls that needed to be serialized. Then, rather than bracketing them each inside a .then() handler to chain them properly, you could just use await and have simpler looking code.

A common mistake with both await and .then() is to forget proper error handling. If your error handling desire in this function is to just return the rejected promise, then both of your versions do that identically. But, if you have multiple async calls in a row and you want to do anything more complex than just returning the first rejection, then the error handling techniques for await and .then()/.catch() are quite different and which seems simpler will depend upon the situation.

Is it possible for an 'await' to resolve to a Promise?

No, it's impossible for an await expression to result in a promise.

Just like you cannot fulfill a promise with another promise, and like then() never calling the fulfillment handler with a promise.

does async/await nesting have performance consequences?

There is nothing in the microtask queue until promises resolve. Until then other tasks and (UI) events can be processed.

This is because the await operator will make the corresponding async function return immediately, allowing other JS code to execute. In your case the promise is returned by fetch, which in practice will not resolve immediately. So there is nothing blocking here.

Then when the HTTP response makes the fetch promise resolve, a microtask will indeed be created, which, when when executed will restore the corresponding async function's execution context. Your example function has nothing else to do, so that is quickly done.

Note that it does not matter whether this function was originally called from within some other function: at this stage, only that particular function's execution context (in which an awaited promise resolved) is restored without any pre-existing callstack. So it does not return again to the wrapping function. That already happened in the first phase and will not happen again.

Then again there is free event processing until the next fetch promise resolves. And so it continues.



Related Topics



Leave a reply



Submit