Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC4 async and Parallel execution

So I'm trying to get my head around this new 'async' stuff in .net 4.5. I previously played a bit with async controllers and the Task Parallel Library and wound up with this piece of code:

Take this model:

public class TestOutput
{
    public string One { get; set; }
    public string Two { get; set; }
    public string Three { get; set; }

    public static string DoWork(string input)
    {
        Thread.Sleep(2000);
        return input;
    }
}

Which is used in a controller like this:

public void IndexAsync()
{
    AsyncManager.OutstandingOperations.Increment(3);

    Task.Factory.StartNew(() => 
        { 
            return TestOutput.DoWork("1"); 
        })
        .ContinueWith(t => 
        { 
            AsyncManager.OutstandingOperations.Decrement(); 
            AsyncManager.Parameters["one"] = t.Result; 
        });
    Task.Factory.StartNew(() =>
        {
            return TestOutput.DoWork("2");
        })
        .ContinueWith(t =>
        {
            AsyncManager.OutstandingOperations.Decrement();
            AsyncManager.Parameters["two"] = t.Result;
        });
    Task.Factory.StartNew(() =>
        {
            return TestOutput.DoWork("3");
        })
        .ContinueWith(t =>
        {
            AsyncManager.OutstandingOperations.Decrement();
            AsyncManager.Parameters["three"] = t.Result;
        });
}

public ActionResult IndexCompleted(string one, string two, string three)
{
    return View(new TestOutput { One = one, Two = two, Three = three });
}

This controller renders the view in 2 seconds, thanks to the magic of the TPL.

Now I expected (rather naively) that the code above would translate into the following, using the new 'async' and 'await' features of C# 5:

public async Task<ActionResult> Index()
{
    return View(new TestOutput
    {
        One = await Task.Run(() =>TestOutput.DoWork("one")),
        Two = await Task.Run(() =>TestOutput.DoWork("two")),
        Three = await Task.Run(() =>TestOutput.DoWork("three"))
    });
}

This controller renders the view in 6 seconds. Somewhere in the translation the code became no longer parallel. I know async and parallel are two different concepts, but somehow I thought the code would work the same. Could someone point out what is happening here and how it can be fixed?

like image 867
Lodewijk Avatar asked Jan 30 '12 21:01

Lodewijk


People also ask

Do async tasks run in parallel?

There is no parallelism here, as the “async Task” does not automatically make something run in in parallel. This will spawn 2 threads, run them simultaneously, and return when both threads are done. This will create a list of Tasks to be run at the same time.

Why we use async and await in MVC?

Async keyword is used to call the function/method as asynchronously. Await keyword is used when we need to get result of any function/method without blocking that function/method. Asynchronous Programming means parallel programming.

When should Async controller actions be used?

You can use asynchronous action methods for long-running, non-CPU bound requests. This avoids blocking the Web server from performing work while the request is being processed. A typical use for the AsyncController class is long-running Web service calls.

What is the use of Async controllers in MVC?

The asynchronous controller enables you to write asynchronous action methods. It allows you to perform long running operation(s) without making the running thread idle. It does not mean it will take lesser time to complete the action.


2 Answers

Somewhere in the translation the code became no longer parallel.

Precisely. await will (asynchronously) wait for a single operation to complete.

Parallel asynchronous operations can be done by starting the actual Tasks but not awaiting them until later:

public async Task<ActionResult> Index() 
{
  // Start all three operations.
  var tasks = new[]
  {
    Task.Run(() =>TestOutput.DoWork("one")), 
    Task.Run(() =>TestOutput.DoWork("two")), 
    Task.Run(() =>TestOutput.DoWork("three"))
  };

  // Asynchronously wait for them all to complete.
  var results = await Task.WhenAll(tasks);

  // Retrieve the results.
  return View(new TestOutput
  {
    One = results[0],
    Two = results[1],
    Three = results[2]
  }); 
} 

P.S. There's also a Task.WhenAny.

like image 96
Stephen Cleary Avatar answered Nov 19 '22 23:11

Stephen Cleary


No, you stated the reason that this is different already. Parallel and Async are two different things.

The Task version works in 2 seconds because it runs the three operations at the same time (as long as you have 3+ processors).

The await is actually what it sounds like, the code will await the execution of the Task.Run before continuing to the next line of code.

So, the big difference between the TPL version and the async version are that the TPL version runs in any order because all of the tasks are independent of each other. Whereas, the async version runs in the order that the code is written. So, if you want parallel, use the TPL, and if you want async, use async.

The point of async is the ability to write synchronous looking code that will not lock up a UI while a long running action is happening. However, this is typically an action that all the processor is doing is waiting for a response. The async/await makes it so that the code that called the async method will not wait for the async method to return, that is all. So, if you really wanted to emulate your first model using async/await (which I would NOT suggest), you could do something like this:

MainMethod()
{
    RunTask1();
    RunTask2();
    RunTask3();
}

async RunTask1()
{
    var one = await Task.Factory.StartNew(()=>TestOutput.DoWork("one"));
    //do stuff with one
}

async RunTask2()
{
    var two= await Task.Factory.StartNew(()=>TestOutput.DoWork("two"));
    //do stuff with two
}

async RunTask3()
{
    var three= await Task.Factory.StartNew(()=>TestOutput.DoWork("three"));
    //do stuff with three
}

The code path will go something like this (if the tasks are long running)

  1. main call to RunTask1
  2. RunTask1 awaits and returns
  3. main call to RunTask2
  4. RunTask2 awaits and returns
  5. main call to RunTask3
  6. RunTask3 awaits and returns
  7. main is now done
  8. RunTask1/2/3 returns and continues doing something with one/two/three
  9. Same as 7, except less the one that already completed
  10. Same as 7, except less the two that already completed

****A big disclaimer about this, though. Await will run synchronously if the task is already completed by the time that the await is hit. This saves the runtime from having to perform its vudu :) since it is not needed. This will make the code flow above incorrect as the flow is now synchronous****

Eric Lippert's blog post on this explains things much better than I am doing :) http://blogs.msdn.com/b/ericlippert/archive/2010/10/29/asynchronous-programming-in-c-5-0-part-two-whence-await.aspx

Hopefully, that helps dispel some of your questions about async versus TPL? The biggest thing to take away is that async is NOT parallel.

like image 30
Justin Pihony Avatar answered Nov 20 '22 00:11

Justin Pihony