References
Like many developers working with Microsoft’s MVC platform, we have frequently leveraged the trio of C# language features, Task, await and async, to help improve performance of long running, I/O blocking processes in our Controller actions and Repositories.
When working with many different languages, platforms and third party tools in a consulting context, it sometimes takes a simple problem to challenge our deeper understanding of how certain API’s, libraries and language features, really work.
Multi-threaded, parallel programming remains a very a deep and challenging area of knowledge, despite the significant convenience features Microsoft has provided with the TPL (Task), await and async language features in C#. The following problem caused us to take a deeper dive into how each of these three features work together and the discovery that the Task class is essentially the same thing as a JavaScript Promise (or Futures in Microsoft-speak), a concept we are very familiar with from working with AngularJS and other front end frameworks. This post describes our discoveries around the Task, async and await language features while attempting to resolve this simple problem.
The Problem
We needed to update an existing WebAPI Controller Action to return a BadRequest
based on a condition in the posted Model.
public Task<IHttpActionResult> Post(ClaimsEditViewModel model)
{
// Example of the code we needed to add
if(model.DateReceived < DateTime.Now)
{
return BadRequest(); // PROBLEM: Compiler didn't like this (red squiggles in Visual Studio)
}
// SaveAsync is awaitable, returns Task<HttpActionResult>
return SaveAsync(model, "Index", "Claims", "Claim successfully saved.");
}
Seems like a simple task. However, the project would not build; the compiler complained about returning BadRequest
(System.Web.Http.Results.ErrorMessageResult) in this method. Seems obvious at first, because the method signature requires returning a Task, not an ErrorMessageResult
.
However, in the same WebApi controller, there was another Action method returning a BadRequest
just fine without any complaint from the compiler:
//fictional method for this example
public async Task<IHttpActionResult> Post2(ClaimsEditViewModel model, int someValue)
{
//pseudo code
if (someValue < 0) {
return BadRequest();
}
// SaveAsync is awaitable, returns Task<HttpActionResult>
return await SaveAsync(model, "Index", "Claims", "Claim successfully saved.");
}
This compiled fine. Huh?
Discoveries
This gave us pause. Why could we not return a BadRequest
in the first method, but return one in the second method? At first blush, both methods appeared asynchronous – both method signatures return a Task<IHttpActionResult>
, and SaveAsync is obviously asynchronous, right? And BadRequest
can be cast to IHttpActionResult
, so what gives?
Well, a few key discoveries/refreshers:
- C# Tasks are Futures (what?) which are Promises (ah, ok!).
- Using Async with
Task<T>
enables some magic, where objects of typeT
are automatically wrapped in newTask
objects for us. We do not have to manually wrap our objects of Type T into new Task objects before returning them. - This automatic wrapping happens regardless of whether or not the method is awaitable (asynchronous) or synchronous.
- Both methods are not asynchronous. Post1 is synchronous, Post2, asynchronous. Post 1 is synchronous because returning an awaitable
Task<T>
likeSaveAsync
without theawait
keyword in a method returning aTask<T>
without theasync
keyword will execute synchronously. We briefly erroneously assumed returningTask<T>
meant asynchronous. - If the original action method could be improved: if it did not need to be asynchronous, returning a Task in a synchronous method is required and may cause a performance hit.
What follows is a paraphrased excerpt from an internal Slack thread where we walked through a deductive process to make the discovery that Task
with Async
automagically wraps returned objects in Tasks.
Breaking it Down – Returning BadRequest
Let’s start by validating our understanding that BadRequest
can cast to IHttpActionResult
in a simplified Action method with a return type of just IHttpActionResult
:
public IHttpActionResult Post(ClaimsEditViewModel model)
{
return BadRequest();
}
This builds and compiles, so we have validated that BadRequest
can be cast to the method’s return type, IHttpActionResult
because of it’s parentage.
But can we return a BadRequest
when the method’s return type is Task<IHttpActionResult>
?
public Task<IHttpActionResult> Post(ClaimsEditViewModel model)
{
return BadRequest(); // red squiggle here: BadRequest is not castable to Task<IHttpActionResult>
}
Another fairly obvious sanity check. This won’t work because the method now expects the BadRequest
wrapped in a Task <IHttpActionResult>
.
To make the above work, we need to do manually wrap the BadRequest
in a Task
. Let’s look at doing this using the original method to which we want to add a conditional test and return a BadRequest
.
public Task<IHttpActionResult> Post(ClaimsEditViewModel model)
{
// Example of the code we needed to add
if(model.DateReceived < DateTime.Now)
{
// manually wrap the BadRequest in a new Task
return new Task<IHttpActionResult>(() => { return BadRequest(); });
}
// SaveAsync is awaitable, returns Task<HttpActionResult>
return SaveAsync(model, "Index", "Claims", "Claim successfully saved.");
}
This compiles because now every IHttpActionResult
is returned, wrapped in a Task
.
But wait, why do we have to wrap the BadRequest
in a new Task
in Post1
, when we don’t have to in Post2
?
public async Task<IHttpActionResult> Post2(ClaimsEditViewModel model, int someValue)
{
if (someValue < 0) {
// Why don't we have to wrap this in a new Task? Why does this work?
return BadRequest();
}
// SaveAsync is awaitable, returns Task<HttpActionResult>
return await SaveAsync(model, "Index", "Claims", "Claim successfully saved.");
}
Obviously, BadRequest
was being wrapped automagically into a Task<IHttpActionResult>
. But why? What it the fact the action method is asynchronous? The fact the await keyword is used together with the async keyword?
It turns out that awaiting execution from methods invoked within the action method really has nothing to do with automagically wrapping returned objects into Tasks. It’s solely the combination of the async keyword with returned Task<T>
that enables this syntactical sugar.
Here, we combine async with Task<T>
to automatically wrap returned objects into Tasks without any asynchronous execution. This compiles and runs.
public async Task<IHttpActionResult> PostSync(ClaimsEditViewModel model, int someValue)
{
if (someValue > 0)
{
return BadRequest();
}
return Save(model, "Index", "Claims");
}
With the above, Visual Studio will generate a green squiggle under PostSync
and inform us that the action will execute synchronously. So it will execute synchronously despite the use of the async keyword and returning a Task. It will wrap the returned BadRequest
into a Task.
So Task<T>
with the async keyword can be used to automagically wrap return objects of type T
to Task<T>
.
But what is the use of returning a Task for a synchronous method? As far as we are aware, none. In fact we suspect it might cause a performance hit (please let us know if we’re wrong).
Wrapping It Up: The Final Method with Task, Await and Async
Our original challenge was was to add a conditional check to an existing MVC Controller Action and return a BadRequest
if it was met. Because the original method invoked the awaitable, asynchronous SaveAsync
method, we chose to update the method to be fully asynchronous, and take advantage of the automagic wrapping of BadRequest
into a Task.
public async Task<IHttpActionResult> Post(ClaimsEditViewModel model)
{
if (model.DateReceived < DateTime.Now)
{
return BadRequest();
}
return await SaveAsync(model, "Index", "Claims", "Claim successfully saved.");
}