TPL Dataflow:在保持秩序的同时设计并行性

我以前从未使用过TPL,所以我想知道是否可以用它完成:我的应用程序从很多帧创建一个gif图像动画文件。 我从一个Bitmap列表开始,它代表了gif文件的帧,需要为每个帧执行以下操作:

  1. 在框架上绘制一些文本/位图
  2. 裁剪框架
  3. 调整框架大小
  4. 将图像缩小为256色

显然,这个过程可以对列表中的所有帧并行完成,但是对于每个帧,步骤的顺序必须相同。 之后,我需要将所有帧写入gif文件。 因此,需要以与原始列表中相同的顺序接收所有帧。 最重要的是,此过程可以在第一帧准备就绪时开始,无需等到所有帧都被处理完毕。

这就是情况。 TPL Dataflow适合这个吗? 如果是的话,任何人都可以给我一个关于如何设计tpl块结构以反映上述过程的正确方向的提示吗? 与我发现的一些样品相比,这对我来说似乎相当复杂。

我认为使用TPL Dataflow是有道理的,特别是因为即使打开并行性,它也会自动保持处理元素的正确顺序。

您可以为流程中的每个步骤创建一个单独的块,但我认为这里没有必要,一个用于处理帧的块和一个用于编写它们的块就足够了:

public Task CreateAnimationFileAsync(IEnumerable frames) { var frameProcessor = new TransformBlock( frame => ProcessFrame(frame), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded }); var animationWriter = new ActionBlock(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) { … } 

我想你会发现DataFlow是正确的方法。 对于每个帧,从帧列表中尝试创建一个TransformBlock 。 对于四个步骤中的每个步骤,以正确的顺序将帧链接在一起。 如果要同时处理帧列表,可以使用bufferblock作为帧列表。

请在msdn上找到有关如何使用transformblock的完整示例:

您的问题是数据流优势所在的完美示例。

这是最简单的代码,可以帮助您入门。

 // Try increasing MaxDegreeOfParallelism var opt = new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 2 }; // Create the blocks // You must define the functions to do what you want var paintBlock = new TransformBlock(fnPaintText, opt); var cropBlock = new TransformBlock(fnCrop, opt); var resizeBlock = new TransformBlock(fnResize, opt); var reduceBlock = new TransformBlock(fnReduce,opt); // Link the blocks together paintBlock.LinkTo(cropBlock); cropBlock.LinkTo(resizeBlock); resizeBlock.LinkTo(reduceBlock); // Send data to the first block // ListOfImages contains your original frames foreach (var img in ListOfImages) { paintBlock.Post(img); } // Receive the modified images var outputImages = new List(); for (int i = 0; i < ListOfImages.Count; i++) { outputImages.Add(reduceBlock.Receive()); } // outputImages now holds all of the frames // reassemble them in order