Dan Parker
Dan Parker

Reputation: 843

What's the best way to call multiple async tasks in a Web API controller?

I'm trying to find the best way to make my API async with multiple dependent calls

<top routes...>
public async Task<IHttpActionResult> Post()
{
    /*... some sync items*/
    int userId= await _ef.saveUser();
    var siteInfo = callsite(userId);
    return await _ef.updateUserLastModified(); //need to wait for other 2
}

So all 3 need to go in order, can I make a private function that runs that as a task? Are there any advantages to make it async? I basically just want it to thread those 3 calls.

Upvotes: 2

Views: 2882

Answers (2)

Nate Barbettini
Nate Barbettini

Reputation: 53710

What you have is fine! Assuming the last line depends on the first two, your code is the correct way to await asynchronous calls and also execute synchronous code:

public async Task<IHttpActionResult> Post()
{
   int userId = await _ef.saveUser();         // 1
   var siteInfo = callsite(userId);           // 2 
   return await _ef.updateUserLastModified(); // 3
}

Line 2 will not execute until line 1 is complete, and then line 3 will be awaited and return. Any synchronous code before the end of the method will also be executed "normally".

Async isn't the same thing as multithreading - it's just a way of telling the compiler that you need to wait for some asynchronous thing to finish before continuing.

Upvotes: 4

David Pine
David Pine

Reputation: 24545

The beauty of async and await is that you can write your code such that it reads like synchronous code. Let's break down what you have, as it is the correct implementation of asynchronous programming.

public async Task<IHttpActionResult> Post()
{
   int userId = await _ef.saveUser();
   var siteInfo = callsite(userId);
   return await _ef.updateUserLastModified();
}

Method Signature

We define our publically accessible async method, that happily returns a Task<IHttpActionResult> and is named Post, great.

Body Line 1

We then are declaring and assigning a variable named userId the awaited result of the _ed.saveUser() invocation. This means that saveUser must return an Task<int>, otherwise this wouldn't compile. The await keyword is possible in this method block since we have used the async keyword in the method signature. When we await the saveUser call the internal async state-machine captures a return location and then immediately returns control to the calling thread -- allowing it to do work elsewhere. After some unknown amount of time, and when the saving of the user is complete control is then resuming where it left off (potentially on the same thread or even on a thread pool thread) and the userId variable is assigned.

Body Line 2

Once we have our userId then and only then do we continue to the next line. If we mistakenly omitted the await on the previous line and errantly declared our userId variable as a var instead of an int, it would have been problematic as the variable would have been a Task<int> rather than the materialized int we desired. Notice, this is synchronous code -- which is perfectly fine and acceptable and in fact encouraged. Not every piece of logic needs to be async, and the phrase "async all the way" actually means something else. It means, for example that instead of our Post method being void and calling .Result on saveUser you instead go async all the way.

Body Line 3

Finally, we are returning the awaited result of the _ef.udateUserLastModified() method. We know that this too returns a Task<IHttpActionResult>.

Upvotes: 2

Related Questions