Fan-in fan-out
ctx.WhenAll schedules several activities together and resumes past the join once all of
them have completed. Like every step, the fan-out is durable: a crash mid-flight resumes from
recorded branch outcomes. A branch whose external side effect completed but whose outcome was not
recorded can execute again, so activities still require idempotent side effects.
Parallel activities with typed results
Section titled “Parallel activities with typed results”Use ctx.CallActivity to create the pending activities, then await them together. Up to
five differently-typed activities come back as a tuple:
var (hello, goodbye) = await ctx.WhenAll( ctx.CallActivity((HelloActivities a) => a.Hello(input.Name)), ctx.CallActivity((HelloActivities a) => a.Goodbye(input.GoodbyeId)), cancellationToken);For more than five differently-typed branches, give the activities a shared result type
and use the collection overload below. Note that splitting one fan-out into two
consecutive ctx.WhenAll calls does not keep everything parallel: activities are only
enqueued when ctx.WhenAll runs, so the first batch completes before the second starts.
Fan-out over a collection
Section titled “Fan-out over a collection”When every branch has the same result type, pass a sequence and get an array back:
var results = await ctx.WhenAll( customers.Select(c => ctx.CallActivity((EmailActivities a) => a.SendNewsletter(c.Email)) ), cancellationToken);The whole batch is enqueued together, so activity workers pick the branches up side by
side; how many actually run at once is bounded by your workers’ MaxConcurrency.
What happens on failure?
Section titled “What happens on failure?”ctx.WhenAll matches Task.WhenAll semantics: it waits for every branch to finish
before surfacing anything, then throws the first failure (by position). Branches whose success was
recorded keep their results; workflow replay memoizes and returns those results without deliberately
scheduling another job. The failure then propagates like any workflow failure: the run fails (after its
workflow-level attempts) and any registered
compensations run.
How it stays durable
Section titled “How it stays durable”Each branch is its own persisted step backed by its own activity job, so a fan-out of fifty is fifty step rows and fifty job rows. While branches execute, the run parks without holding a worker slot. Currently every branch completion attempts to wake the parent; an early replay observes outstanding siblings and parks again, while the replay after the last completion passes the join. Recorded branches return their stored results, and unresolved branches are waited for again.