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?
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.
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.
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.
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.
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 Task
s but not await
ing 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
.
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)
****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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With