I have a method (called via an AJAX request) that runs at the end of a sequence. In this method I save to the database, send emails, look up a bunch of info in other APIs/databases, correlate things together, etc.. I just refactored the original method into a second revision and used Task
s to make it asynchronous, and shaved off up to two seconds in wall time. I used Task
s mainly because it seemed easier (I'm not that experienced in async/await yet) and some tasks depend on other tasks (like task C D and E all depend on results from B, which itself depends on A). Basicalll all of the tasks are started at the same time (processing just zips down the to the Wait()
call on the email task, which in one way or another requires all the others to complete. I generally do something like this except with something like eight tasks:
public thing() {
var FooTask<x> = Task<x>.Factory.StartNew(() => {
// ...
return x;
});
var BarTask<y> = Task<y>.Factory.StartNew(() => {
// ...
var y = FooTask.Result;
// ...
return y + n;
}
var BazTask<z> = Task<z>.Factory.StartNew(() => {
var y = FooTask.Result;
return y - n;
}
var BagTask<z> = Task<z>.Factory.StartNew(() => {
var g = BarTask.Result;
var h = BazTask.Result;
return 1;
}
// Lots of try/catch aggregate shenanigans.
BagTask.Wait();
return "yay";
}
Oh I also need to roll back previous things if something breaks, like remove a database row if the email fails to send, so there are a few levels of try/catches in there. Anyway, all of this works (amazingly, it all worked on the first try). My question is whether this sort of method would benefit from being rewritten to use async/await rather than Task
s. If so, how would the multiple-dependency scenario play out without re-running an async method that was already ran or awaited by another method? I guess some shared variable?
Update:
The // ...
lines were supposed to indicate that the task was doing something, like looking up DB records. Sorry if that wasn't clear. About half of the tasks (there are 8 total) can take up to maybe five seconds to run, if the contexts aren't warmed up, and the other half of the tasks just collect/assemble/process/use that data.
You'll find that
async
/await
(paired withTask.Run
instead ofStartNew
) will make your code much cleaner:You also have the option of using
Task.WhenAll
if you want toawait
multiple tasks completing. Error handling in particular is cleaner withawait
since it doesn't wrap exception inAggregateException
.However
This is a bit of a problem. Both
StartNew
andTask.Run
should be avoided on ASP.NET.Yes, parallel processing on ASP.NET (which is what the code is currently doing) will make individual requests execute faster, but at the expense of scalability. The server will be unable to handle as many requests if it is doing parallel processing on each one.
These are all I/O-bound operations, not CPU-bound. So the ideal solution is to create truly-async I/O methods and then just call them using
await
(andTask.WhenAll
if necessary). By "truly-async", I mean calling the underlying asynchronous APIs (e.g.,HttpClient.GetStringAsync
instead ofWebClient.DownloadString
; or Entity Framework'sToListAsync
instead ofToList
, etc). UsingStartNew
orTask.Run
is what I call "fake asynchrony".Once you have asynchronous APIs, your top-level method really becomes simple: