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:
async
does not equal multiple threads; and- making a function
async Task
does not make it asynchronous
When Task.WhenAll
is run with pretend async code that has no await
s 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
Check If the Current User Is Administrator
Access to Foreach Variable in Closure Warning
C# - Capturing the Mouse Cursor Image
Addeventhandler Using Reflection
Virtual Webcam Input as Byte Stream
Calling a Generic Method with a Dynamic Type
Is Endinvoke() Optional, Sort-Of Optional, or Definitely Not Optional
How to Initialize a List<T> to a Given Size (As Opposed to Capacity)
Should You Declare Methods Using Overloads or Optional Parameters in C# 4.0
Wpf C# Path: How to Get from a String with Path Data to Geometry in Code (Not in Xaml)
How to Make a Hyperlink Work in a Richtextbox
JSON String to CSV and CSV to JSON Conversion in C#
Creating Delegates Manually VS Using Action/Func Delegates