Osy#betaRun it on Osyrin: osyrin.com

Reference / Function

Wait for several tasks (Task.WhenAll, Task.WhenAny)

await Task.WhenAll(a, b, …) · await Task.WhenAll(tasks) · await Task.WhenAny(a, b, …) · Task.FromResult(v) · Task.CompletedTask

Task.WhenAll waits for every task and answers their results in order; Task.WhenAny answers the first task to finish, and the others keep running, as in C# — hand them a CancellationToken to stop them. Task.WaitAll and Task.WaitAny are the same joins written C#'s blocking way. Task.FromResult and Task.CompletedTask are already finished.

stable4 examples compiled by CIfunctionconcurrencystdlib

Summary#

Start several tasks, then wait for them together. await Task.WhenAll(a, b) returns when every task has finished and answers their results as an array, in the order you passed them. await Task.WhenAny(a, b) returns the first task to finish — await that to get its value.

int[] both  = await Task.WhenAll(Count(x), Count(y));
var  first  = await Task.WhenAny(Ask(primary), Ask(backup));
var  answer = await first;

Signature#

await Task.WhenAll(a, b, …)        // Task<T[]> — every result, in operand order
await Task.WhenAll(tasks)          // …over a List<Task<T>> (or any collection of tasks)
await Task.WhenAny(a, b, …)        // Task<Task<T>> — the first task to finish
Task.WaitAll(a, b, …)              // the WhenAll join, written as a statement
Task.WaitAny(a, b, …)              // int — the index of the first task to finish
Task.FromResult(value)             // a Task<T> already finished with value
Task.FromException(ex)             // a Task already failed with ex (FromException<T> for a Task<T>)
Task.FromCanceled(token)           // a Task already cancelled
Task.CompletedTask                 // a Task already finished
await Task.Yield()                 // accepted; a task already finished

Description#

What does WhenAll answer when one fails?#

Task.WhenAll waits for every task, even after one has failed — then rethrows the first failure, in the order you passed the tasks. That is C#'s rule: nothing is still running when the error reaches you. Over tasks with no result (Task), it answers nothing.

Which task does WhenAny pick?#

The first to finish, whether it succeeded or failed — as C# answers it. Awaiting the winner gives its value or rethrows its failure. When several tasks are already finished, the first of them in the order you passed.

What happens to the tasks that lost?#

They keep running, as in C#, and the run still waits for them before it ends — work a run started is never left behind. To stop them, do what C# does: hand each task a CancellationToken and cancel it once you have the winner. A task handed a token stops when it is cancelled — its waits and its outbound calls with it — even if it never reads the token itself (see Stop a task (CancellationToken) and complete one by hand (TaskCompletionSource)).

Are Task.WaitAll and Task.WaitAny blocking?#

They are the same joins, written as C# writes a blocking wait: Task.WaitAll(a, b) waits for both, Task.WaitAny(a, b) answers the index of the first to finish. Nothing here holds a thread, so neither can deadlock; t.Wait(), t.Result and t.GetAwaiter().GetResult() are joins in the same way. As in C#, the blocking forms raise an AggregateException over the failure — Task.WaitAll over EVERY failure, in operand order — where await raises the first failure itself.

Can I build the list in a loop?#

Yes — that is the fan-out over data whose size is known only at run time. Add each started task to a List<Task<T>> and pass the list.

Can a task wait for a person?#

In a workflow body, yes: a function started as a task may await a child workflow run, and Task.WhenAll / Task.WhenAny over it hold for as long as that takes — the run parks, and resumes with the results (Tasks that wait (Task.WhenAll across days)). Outside a workflow a task runs beside its caller from beginning to end.

Can a race have a timeout?#

Task.Delay is a task too, so the C# timeout is written the C# way — race the work against a delay, and see which won.

Examples#

async Task<int> Score(int n) { return n * n; }

async Task<int> Total(int count) {
  var tasks = new List<Task<int>>();
  for (var i = 1; i <= count; i++) { tasks.Add(Score(i)); }
  var scores = await Task.WhenAll(tasks);
  return scores.Sum();
}
async Task<string> Slow() {
  await Task.Delay(TimeSpan.FromSeconds(30));
  return "late";
}

async Task<string> WithTimeout() {
  var work = Slow();
  var limit = Task.Delay(TimeSpan.FromSeconds(2));
  var first = await Task.WhenAny(work, limit);
  return first == work ? await work : "timed out";
}
async Task<int> Fast() { return 1; }
async Task<int> Patient(CancellationToken token) {
  await Task.Delay(TimeSpan.FromSeconds(30), token);
  return 2;
}

async Task<int> FirstThenStop() {
  var cts = new CancellationTokenSource();
  var winner = await Task.WhenAny(Fast(), Patient(cts.Token));
  cts.Cancel();
  return await winner;
}
Task<int> Known(int n) { return Task.FromResult(n); }

async Task<int> Sum() {
  await Task.CompletedTask;
  var both = await Task.WhenAll(Known(2), Known(3));
  return both[0] + both[1];
}

See also#

Related

async / await and Task

C#'s async and await, with C#'s meaning. A call to a Task-returning method that you do not await where you make it…

Wait in place (Task.Delay)

Wait before carrying on — between checks on a job another system is still running, before trying a call again, or for a…

Stop a task (CancellationToken) and complete one by hand (TaskCompletionSource)

C#'s cancellation, with C#'s meaning. A CancellationTokenSource hands out a Token; cancel the source and every task…

Parallel legs (start several, then wait for them)

Start several pieces of work at once, each with its own compensation, and wait for them together. Writing…