Home > database >  ASP.NET Core Endpoint of type Task<T> returns fine without any await...how?
ASP.NET Core Endpoint of type Task<T> returns fine without any await...how?

Time:01-15

So this is a random question that came about from a discussion over what I consider the over usage of await in projects at work...

Not sure why I never tried it until now, other than the fact that it's weird and random, but the fact it does work just makes me wonder...how?

The .Result usage was only used to get the responseMessage. I know that is blocking and no bueno and it is for demo purposes only...

So this endpoint works fine...nothing is awaited

[HttpGet("kitteh")]
public Task<string> GetCatFact()
{
    var client = new HttpClient();
    var res = client.GetAsync("https://catfact.ninja/fact").Result;
    return res.Content.ReadAsStringAsync();
}

There's obviously something in the default ASP.NET pipleline that ultimately unwraps the task in order to return the result...but where...or how?

Is this "less efficient" than awaiting in the endpoint itself as the magic taking place behind the scenes is ultimately just blocking to get the result of the returned task?

CodePudding user response:

Your assumption here is basically correct...

There's obviously something in the default ASP.NET pipleline that ultimately unwraps the task in order to return the result

The framework supports asynchronous controller actions. In order to do so, it would need to inspect the return value of your methods. If the method returns a Task, it will ultimately await on the result before responding.

Even if your action itself does all the awaiting, it still has to return a Task so the caller will still wait (the alternative being some ugly blocking code).

As has been pointed out in some other posts, there are some performance improvements to be had by not awaiting a returned Task so I would write your action as

public async Task<string> GetCatFact()
{
    var client = new HttpClient();
    var res = await client.GetAsync("https://catfact.ninja/fact");
    return res.Content.ReadAsStringAsync(); // no await
}

Your controller handles waiting for the remote response but delegates waiting for the content stream to the caller.

CodePudding user response:

There's obviously something in the default ASP.NET pipleline that ultimately unwraps the task in order to return the result...but where...or how?

ASP.NET asynchronously waits for your task to complete, and then it sends the HTTP response based on the result of the task. It's logically similar to await: an asynchronous wait.

Is this "less efficient" than awaiting in the endpoint itself as the magic taking place behind the scenes is ultimately just blocking to get the result of the returned task?

Yes. It is less efficient to block.

ASP.NET doesn't block; it asynchronously waits. Blocking ties up a thread. So when the code calls .Result, it will be using a thread just to wait for that HttpClient call to complete.

The proper solution is to keep async and await:

[HttpGet("kitteh")]
public async Task<string> GetCatFact()
{
  var client = new HttpClient();
  var res = await client.GetAsync("https://catfact.ninja/fact");
  return await res.Content.ReadAsStringAsync();
}

This way, while the GetAsync is in progress, the thread is yielded back to the ASP.NET runtime and is available for handling other requests, instead of being blocked waiting for the GetAsync to complete.

More information: Task<string> is part of the method signature. ASP.NET has special understanding of the Task<T> type and knows to asynchronously wait for it. async is not part of the method signature. ASP.NET knows whether your method returns Task, but it has no idea whether it's async (and doesn't care). So, in some situations, it's OK to elide the keywords (as described on my blog, but only when the method implementation is trivial. If there's any logic in the method, keep the async and await.

  •  Tags:  
  • Related