Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the correct way to run multiple parallel tasks in an asp.net process?

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.

like image 820
George Mauer Avatar asked Jan 16 '15 15:01

George Mauer


People also ask

How do I run two tasks in parallel?

If you have several tasks that can be run in parallel, but still need to wait for all the tasks to end, you can easily achieve this using the Task. WhenAll() method in . NET Core. This will upload the first file, then the next file.

What is parallel tasking?

Task parallelism (also known as function parallelism and control parallelism) is a form of parallelization of computer code across multiple processors in parallel computing environments. Task parallelism focuses on distributing tasks—concurrently performed by processes or threads—across different processors.

What is Task Parallel Library in C#?

The Task Parallel Library (TPL) is a set of public types and APIs in the System. Threading and System. Threading. Tasks namespaces. The purpose of the TPL is to make developers more productive by simplifying the process of adding parallelism and concurrency to applications.

What is Task WhenAll?

Task. WhenAll creates a task that will complete when all of the supplied tasks have been completed. It's pretty straightforward what this method does, it simply receives a list of Tasks and returns a Task when all of the received Tasks completes.


2 Answers

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 with ConfigureAwait(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 having IsCompleted return false and OnCompleted execute the Action parameter immediately). ConfigureAwait(false) disregards the SynchronizationContext and so forces the rest of the method to execute on a ThreadPool thread.

If you use both together you can make sure an async method returns a task immediately which will execute on a ThreadPool thread (like Task.Run):

async Task CreateFileFromLongRunningComputation(int input)
{
    await Task.Yield().ConfigureAwait(false);
    // executed on a ThreadPool thread
}

Edit: George Mauer pointed out that since Task.Yield returns YieldAwaitable you can't use ConfigureAwait(false) which is a method on the Task 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:

async Task CreateFileFromLongRunningComputation(int input)
{
    await Task.Delay(1).ConfigureAwait(false);
    // executed on a ThreadPool thread
}

A better option would be to create a YieldAwaitable that simply disregards the SynchronizationContext the same as using ConfigureAwait(false) does:

async Task CreateFileFromLongRunningComputation(int input)
{
    await new NoContextYieldAwaitable();
    // executed on a ThreadPool thread
}

public struct NoContextYieldAwaitable
{
    public NoContextYieldAwaiter GetAwaiter() { return new NoContextYieldAwaiter(); }
    public struct NoContextYieldAwaiter : INotifyCompletion
    {
        public bool IsCompleted { get { return false; } }
        public void OnCompleted(Action continuation)
        {
            var scheduler = TaskScheduler.Current;
            if (scheduler == TaskScheduler.Default)
            {
                ThreadPool.QueueUserWorkItem(RunAction, continuation);
            }
            else
            {
                Task.Factory.StartNew(continuation, CancellationToken.None, TaskCreationOptions.PreferFairness, scheduler);
            }
        }

        public void GetResult() { }
        private static void RunAction(object state) { ((Action)state)(); }
    }
}

This isn't a recommendation, it's an answer to your Task.Yield questions.

like image 166
i3arnon Avatar answered Sep 28 '22 07:09

i3arnon


(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 are awaiting some async method in it and the ConfigureAwait(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.

like image 36
rossipedia Avatar answered Sep 28 '22 07:09

rossipedia