I think it makes sense to use TPL Dataflow for this, especially since it automatically keeps the processed elements in the right order, even with parallelism turned on.
You could create a separate block for each step in the process, but I think there is no need for that here, one block for processing the frames and one for writing them will be enough:
public Task CreateAnimationFileAsync(IEnumerable<Bitmap> frames)
{
var frameProcessor = new TransformBlock<Bitmap, Bitmap>(
frame => ProcessFrame(frame),
new ExecutionDataflowBlockOptions
{ MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded });
var animationWriter = new ActionBlock<Bitmap>(frame => WriteFrame(frame));
frameProcessor.LinkTo(
animationWriter,
new DataflowLinkOptions { PropagateCompletion = true });
foreach (var frame in frames)
{
frameProcessor.Post(frame);
}
frameProcessor.Complete();
return animationWriter.Completion;
}
private Bitmap ProcessFrame(Bitmap frame)
{
…
}
private async Task WriteFrame(Bitmap frame)
{
…
}