Executing Tasks in Parallel

Executing tasks in parallel

You should almost never use the Task constructor directly. In your case that task only fires the actual task that you can't wait for.

You can simply call DoWork and get back a task, store it in a list and wait for all the tasks to complete. Meaning:

tasks.Add(DoWork());
// ...
await Task.WhenAll(tasks);

However, async methods run synchronously until the first await on an uncompleted task is reached. If you worry about that part taking too long then use Task.Run to offload it to another ThreadPool thread and then store that task in the list:

tasks.Add(Task.Run(() => DoWork()));
// ...
await Task.WhenAll(tasks);

How to best run two async tasks in parallel and wait for the returned results of both?

You can use the async and await pattern and await the cpu bound tasks, however you will need to use async Task Main() entry point

public static async Task Main()
{
var task1 = Task.Run(() => CpuWork1(1));
var task2 = Task.Run(() => CpuWork2(10));

var result1 = await task1;
var result2 = await task2;

// or

var results = await Task.WhenAll(task1, task2) ;
}

If your workloads are IO bound, then they would have the async Task<T> signature, and you would just await the methods returned tasks, similar to above (and not use Task.Run)

Complete Example

static async Task Main()
{

var stopWatch = new Stopwatch();
stopWatch.Start();

var task1 = Task.Run(() => DoSomeWork(1));
var task2 = Task.Run(() => DoSomeOtherWork(10));

var results = await Task.WhenAll(task1, task2);
stopWatch.Stop();

Debug.WriteLine($"Sum: {results.Sum()}");
Debug.WriteLine($"Final: {stopWatch.Elapsed}");
}

Or similarly with an async methods

static async Task Main()
{

var stopWatch = new Stopwatch();
stopWatch.Start();

var task1 = DoSomeWorkAsync(1);
var task2 = DoSomeOtherWorkAsync(10);

var results = await Task.WhenAll(task1, task2);
stopWatch.Stop();

Debug.WriteLine($"Sum: {results.Sum()}");
Debug.WriteLine($"Final: {stopWatch.Elapsed}");
}

private static async Task<int> DoSomeOtherWorkAsync(int waitFor)
{
// some IO bound workload
await Task.Delay(waitFor * 1000);
return waitFor;
}

private static async Task<int> DoSomeWorkAsync(int waitFor)
{
// some IO bound workload
await Task.Delay(waitFor * 1000);
return waitFor;
}

Run two async tasks in parallel and collect results in .NET 4.5

You should use Task.Delay instead of Sleep for async programming and then use Task.WhenAll to combine the task results. The tasks would run in parallel.

public class Program
{
static void Main(string[] args)
{
Go();
}
public static void Go()
{
GoAsync();
Console.ReadLine();
}
public static async void GoAsync()
{

Console.WriteLine("Starting");

var task1 = Sleep(5000);
var task2 = Sleep(3000);

int[] result = await Task.WhenAll(task1, task2);

Console.WriteLine("Slept for a total of " + result.Sum() + " ms");

}

private async static Task<int> Sleep(int ms)
{
Console.WriteLine("Sleeping for {0} at {1}", ms, Environment.TickCount);
await Task.Delay(ms);
Console.WriteLine("Sleeping for {0} finished at {1}", ms, Environment.TickCount);
return ms;
}
}

Trying to run multiple tasks in parallel with .WhenAll but tasks aren't being run in parallel

In short:

  1. async does not equal multiple threads; and
  2. making a function async Task does not make it asynchronous

When Task.WhenAll is run with pretend async code that has no awaits the current thread cannot 'let go' of the task at hand and it cannot start processing another task.

As it was pointed out in the comments, the build chain warns you about it with:
This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Trivial example

Let's consider two function with identical signatures, one with async code and one without.

static async Task DoWorkPretendAsync(int taskId)
{
Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId} -> task:{taskId} > start");
Thread.Sleep(TimeSpan.FromSeconds(1));
Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId} -> task:{taskId} > done");
}

static async Task DoWorkAsync(int taskId)
{
Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId} -> task:{taskId} > start");
await Task.Delay(TimeSpan.FromSeconds(1));
Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId} -> task:{taskId} > done");
}

If we test them with the following snippet

await DoItAsync(DoWorkPretendAsync);
Console.WriteLine();
await DoItAsync(DoWorkAsync);

async Task DoItAsync(Func<int, Task> f)
{
var tasks = Enumerable.Range(start: 0, count: 3).Select(i => f(i));
Console.WriteLine("Before WhenAll");
await Task.WhenAll(tasks);
Console.WriteLine("After WhenAll");
}

we can see that with DoWorkPretendAsync the tasks are executed sequentially.

Before WhenAll
Thread: 1 -> task:0 > start
Thread: 1 -> task:0 > done
Thread: 1 -> task:1 > start
Thread: 1 -> task:1 > done
Thread: 1 -> task:2 > start
Thread: 1 -> task:2 > done
After WhenAll

Before WhenAll
Thread: 1 -> task:0 > start
Thread: 1 -> task:1 > start
Thread: 1 -> task:2 > start
Thread: 5 -> task:0 > done
Thread: 5 -> task:2 > done
Thread: 7 -> task:1 > done
After WhenAll

Things to note:

  • even with real async all tasks are started by the same thread;
  • in this particular run two of the task are finished by the same thread (id:5). This is not guaranteed at all - a task can be started on one thread and continue later on another thread in the pool.


Related Topics



Leave a reply



Submit