BobSwanson BobSwanson - 2 months ago 9
ASP.NET (C#) Question

execute pair of tasks in Parallel

Having two tasks that execute On/Off, I would like to create list of these (pairs) and execute them in Parallel. Meaning: each Task encapsulates TaskA and TaskB - and TaskB always waits for TaskA to finish first. So I should end up with Parallel execution of "set" of TaskA/TaskB


Task = {TaskA, TaskB} , {TaskA, TaskB}, {TaskA, TaskB}
: execute those 3 Sets of tasks in Parallel


private static async Task TaskA()
{
await Task.Delay(2000);
Console.WriteLine("ended TaskA");
}

private static async Task TaskB()
{
await Task.Delay(2000);
Console.WriteLine("ended TaskB");
}

static void StartTask()
{
Task.Run(() =>
{
TaskA().Wait();
TaskB().Wait();
});
}


Main:

List<Task> tasks = new List<Task>();
int taskCount = 3;

for (var i = 0; i < taskCount; i++)
{
var task = new Task(StartTask);
tasks.Add(task);
}

Parallel.ForEach(tasks, task => task.Start());


Currently I'm getting this:

ended TaskA
ended TaskA
ended TaskA
ended TaskB
ended TaskB
ended TaskB


I believe the output should be:

ended TaskA
ended TaskB
ended TaskA
ended TaskB
// etc..


Also, tried this, but still same issue:

static void StartTask()
{
Task task = TaskA().ContinueWith(x => TaskB().Wait(), TaskContinuationOptions.OnlyOnRanToCompletion);
}


Edit 1:

Parallel.For(1, taskCount, (i, state) =>
{
TaskA().ContinueWith(x => TaskB().Wait(), TaskContinuationOptions.OnlyOnRanToCompletion);
});

Answer

I believe the output should be: ...

No, that's not what "parallel" means. If you're going to start three pairs, then I would expect the output you're already seeing.

That said, there's a few things in the code that don't make sense. First off, distinguish between "parallel" (multiple threads) and "concurrent" (doing multiple things at the same time). It looks like your tasks are naturally asynchronous, so let's rewrite things using asynchronous programming instead of threading:

  • Replace Wait with await.
  • Get rid of the Task.Run.

It's easier to just compose TaskA and TaskB into a new "pair task":

static async Task PairTaskAsync()
{
  await TaskA();
  await TaskB();
}

Now you just want to run 3 of these "pair tasks" concurrently. The old code is using the task constructor and then parallelizes starting the tasks - which is pretty much completely wrong, even if we wanted to do parallelism. Which we don't; we want asynchronous concurrency.

Asynchronous concurrency is usually achieved with Task.WhenAll:

List<Task> tasks = new List<Task>();
int taskCount = 3;

for (var i = 0; i < taskCount; i++)
  tasks.Add(PairTaskAsync());

await Task.WhenAll(tasks);

or, for maximum succinctness:

await Task.WhenAll(Enumerable.Range(0, 3).Select(_ => PairTaskAsync()));