I think I'm not understanding something. I had thought that Task.Yield()
forced a new thread/context to be started for a task but upon re-reading this answer it seems that it merely forces the method to be async. It will still be on the same context.
What's the correct way - in an asp.net process - to create and run multiple tasks in parallel without causing deadlock?
In other words, suppose I have the following method:
async Task createFileFromLongRunningComputation(int input) {
//many levels of async code
}
And when a certain POST route is hit, I want to simultaneously launch the above methods 3 times, return immediately, but log when all three are done.
I think I need to put something like this into my action
public IHttpAction Post() {
Task.WhenAll(
createFileFromLongRunningComputation(1),
createFileFromLongRunningComputation(2),
createFileFromLongRunningComputation(3)
).ContinueWith((Task t) =>
logger.Log("Computation completed")
).ConfigureAwait(false);
return Ok();
}
What needs to go into createFileFromLongRunningComputation
? I had thought Task.Yield
was correct but it apparently is not.
The correct way to offload concurrent work to different threads is to use
Task.Run
as rossipedia suggested.The best solutions for background processing in ASP.Net (where your
AppDomain
can be recycled/shut down automatically together with all your tasks) are in Scott Hanselman and Stephen Cleary's blogs (e.g. HangFire)However, you could utilize
Task.Yield
together withConfigureAwait(false)
to achieve the same.All
Task.Yield
does is return an awaiter that makes sure the rest of the method doesn't proceed synchronously (by havingIsCompleted
returnfalse
andOnCompleted
execute theAction
parameter immediately).ConfigureAwait(false)
disregards theSynchronizationContext
and so forces the rest of the method to execute on aThreadPool
thread.If you use both together you can make sure an
async
method returns a task immediately which will execute on aThreadPool
thread (likeTask.Run
):Edit: George Mauer pointed out that since
Task.Yield
returnsYieldAwaitable
you can't useConfigureAwait(false)
which is a method on theTask
class.You can achieve something similar by using
Task.Delay
with a very short timeout, so it wouldn't be synchronous but you wouldn't waste much time:A better option would be to create a
YieldAwaitable
that simply disregards theSynchronizationContext
the same as usingConfigureAwait(false)
does:This isn't a recommendation, it's an answer to your Task.Yield questions.
(l3arnon's answer is the correct one. This answer is more of a discussion on whether the approach posed by the OP is a good one.)
You don't need anything special, really. The
createFileFromLongRunningComputation
method doesn't need anything special, just make sure you areawait
ing some async method in it and theConfigureAwait(false)
should avoid the deadlock, assuming you're not doing anything out of the ordinary (probably just file I/O, given the method name).Caveat:
This is risky. ASP.net will most likely pull the rug out from under you in this situation if the tasks take too long to finish.
As one of the commenters pointed out, there are better ways of accomplishing this. One of them is
HostingEnvironment.QueueBackgroundWorkItem
(which is only available in .NET 4.5.2 and up).If the long running computation takes a significantly long time to complete, you're probably better off keeping it out of ASP.net entirely. In that situation, a better method would be to use some sort of message queue, and a service that processes those messages outside of IIS/ASP.net.