TransformBlock永远不会完成

时间:2014-11-28 10:47:10

标签: c# .net task-parallel-library tpl-dataflow

我正试图在TPL Dataflow块中完成“完成”。特别是TransformBlock似乎没有完成。为什么呢?

示例程序

我的代码计算从1到1000的所有整数的平方。我使用了BufferBlockTransformBlock。稍后在我的代码中,我等待完成TransformBlock。该块实际上从未实际完成,我不明白为什么。

static void Main(string[] args)
{
    var bufferBlock = new BufferBlock<int>();
    var calculatorBlock = new TransformBlock<int, int>(i =>
    {
        Console.WriteLine("Calculating {0}²", i);
        return (int)Math.Pow(i, 2);
    }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 8 });

    using (bufferBlock.LinkTo(calculatorBlock, new DataflowLinkOptions { PropagateCompletion = true }))
    {
        foreach (var number in Enumerable.Range(1, 1000))
        {
            bufferBlock.Post(number);
        }

        bufferBlock.Complete();

        // This line never completes
        calculatorBlock.Completion.Wait();

        // Unreachable code
        IList<int> results;
        if (calculatorBlock.TryReceiveAll(out results))
        {
            foreach (var result in results)
            {
                Console.WriteLine("x² = {0}", result);
            }
        }
    }
}

起初我以为我创造了一个死锁情况,但这似乎不是真的。当我在调试器中检查calculatorBlock.Completion任务时,其Status属性设置为WaitingForActivation。那是我的大脑蓝色被筛选的那一刻。

3 个答案:

答案 0 :(得分:8)

您的管道挂起的原因是,BufferBlockTransformBlock显然都没有完成,直到他们清空了自己的项目(我猜想IPropagatorBlock的所需行为虽然我还没有找到相关文件。)

这可以通过一个更小的例子进行验证:

var bufferBlock = new BufferBlock<int>();
bufferBlock.Post(0);
bufferBlock.Complete();
bufferBlock.Completion.Wait();

除非您在完成前添加bufferBlock.Receive();,否则无限期阻止。

如果您在阻止之前通过TryReceiveAll代码块阻止管道中的项目,将另一个ActionBlock连接到管道,将TransformBlock转换为ActionBlock或任何其他方式,这将不再阻止。


关于您的具体解决方案,您似乎根本不需要BufferBlockTransformBlock,因为块有自己的输入队列而您不会使用返回TransformBlock的值。只需ActionBlock

就可以实现这一目标
var block = new ActionBlock<int>(
    i =>
    {
        Console.WriteLine("Calculating {0}²", i);
        Console.WriteLine("x² = {0}", (int)Math.Pow(i, 2));
    },
    new ExecutionDataflowBlockOptions {MaxDegreeOfParallelism = 8});
foreach (var number in Enumerable.Range(1, 1000))
{
    block.Post(number);
}
block.Complete();
block.Completion.Wait();

答案 1 :(得分:7)

我现在明白了。 TransformBlock的实例不被视为&#34;完成&#34;直到满足以下条件:

  1. TransformBlock.Complete()被称为
  2. InputCount == 0 - 该块已将其转换应用于每个传入元素
  3. OutputCount == 0 - 所有已转换的元素都已离开输出缓冲区
  4. 在我的程序中,没有链接到源TransformBlock的目标块,因此源块永远不会刷新其输出缓冲区。

    作为一种解决方法,我添加了第二个用于存储转换元素的BufferBlock

    static void Main(string[] args)
    {
        var inputBufferBlock = new BufferBlock<int>();
        var calculatorBlock = new TransformBlock<int, int>(i =>
        {
            Console.WriteLine("Calculating {0}²", i);
            return (int)Math.Pow(i, 2);
        }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 8 });
        var outputBufferBlock = new BufferBlock<int>();
        using (inputBufferBlock.LinkTo(calculatorBlock, new DataflowLinkOptions { PropagateCompletion = true }))
        using (calculatorBlock.LinkTo(outputBufferBlock, new DataflowLinkOptions { PropagateCompletion = true }))
        {
            foreach (var number in Enumerable.Range(1, 1000))
            {
                inputBufferBlock.Post(number);
            }
    
            inputBufferBlock.Complete();
            calculatorBlock.Completion.Wait();
    
            IList<int> results;
            if (outputBufferBlock.TryReceiveAll(out results))
            {
                foreach (var result in results)
                {
                    Console.WriteLine("x² = {0}", result);
                }
            }
        }
    }
    

答案 2 :(得分:0)

TransformBlock需要一个ITargetBlock,他可以在其中发布转换。

 var writeCustomerBlock = new ActionBlock<int>(c => Console.WriteLine(c));
        transformBlock.LinkTo(
            writeCustomerBlock, new DataflowLinkOptions
            {
                PropagateCompletion = true
            });

此操作完成。