TransformBlock Items卡在输出队列中。为什么以及如何修复?

时间:2015-09-30 20:39:54

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

我已经浏览了TPL DataFlow,并且在使用与TrasformBlock关联的ActionBlock的代码中遇到了一个非常恼人的问题。

最终我发现项目卡在TransformBlock的输出队列中,因为它的OutputCount属性连续返回高于“0”的值。 这就是为什么整个应用程序陷入僵局。但是,只要我拨打TransformBlock.TryReceiveAll(),就会取消阻止。

任何人,请告诉我,如果我错过了什么或如何防止这种行为?

static void Main()
{
    int total = 0;
    int itemsProcessing = 0;

    TransformBlock<int, Tuple<int, double>> transformBlock = new TransformBlock<int, Tuple<int, double>>(
        i => new Tuple<int, double>(i, Math.Sqrt(i)),
        new ExecutionDataflowBlockOptions
        {
            BoundedCapacity = 20,
            MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded
        });

    ActionBlock<Tuple<int, double>> outputBlock = new ActionBlock<Tuple<int, double>>(async tuple =>
        {
            await Task.Delay(1000); // simulating data output delay
            Interlocked.Decrement(ref itemsProcessing);
        },
        new ExecutionDataflowBlockOptions
        {
            BoundedCapacity = 5,
            MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded
        });

    transformBlock.Completion.ContinueWith(t => outputBlock.Complete());

    using (Timer timer = new Timer(o =>
        {
            Console.Title = string.Format(
                "{0}: {1}/{2} {3}/{4}/{5}",
                Assembly.GetExecutingAssembly().GetName().Name,
                Volatile.Read(ref itemsProcessing), Volatile.Read(ref total),
                transformBlock.InputCount, transformBlock.OutputCount, outputBlock.InputCount);
        }, null, 100, 100))
    {
        using (transformBlock.LinkTo(outputBlock, new DataflowLinkOptions { PropagateCompletion = true }))
        {
            for (int i = 0; i < 40; i++)
            {
                Thread.Sleep(100); // simulating new item retrieval delay

                Interlocked.Increment(ref total);
                Interlocked.Increment(ref itemsProcessing);

                transformBlock.SendAsync(i).Wait();
            }
        }

        Console.WriteLine("Enqueued");

        transformBlock.Complete();
        outputBlock.Completion.Wait();

        Console.WriteLine("Finish");

        timer.Change(Timeout.Infinite, Timeout.Infinite);
        timer.Dispose();
    }
}

1 个答案:

答案 0 :(得分:1)

调用TransformBlock.LinkTo会让您退回一次性注册。当您处置该注册时,块将取消链接。

using范围过早结束,并且在TransformBlock有机会将自身清空到ActionBlock以阻止其无法完成之前,块会取消链接。由于第一个区块没有完成,下一个区块甚至没有开始完成,更不用说完成了。

using块内移动等待解决了死锁问题:

using (transformBlock.LinkTo(outputBlock, new DataflowLinkOptions { PropagateCompletion = true }))
{
    for (int i = 0; i < 40; i++)
    {
        Thread.Sleep(100); // simulating new item retrieval delay

        Interlocked.Increment(ref total);
        Interlocked.Increment(ref itemsProcessing);

        transformBlock.SendAsync(i).Wait();
    }

    Console.WriteLine("Enqueued");
    transformBlock.Complete();
    outputBlock.Completion.Wait();
    Console.WriteLine("Finish");
}

作为旁注,你真的不应该以这种方式阻止异步代码。使用async-await而不是Wait()Task.Delay而不是Thread.Sleep等会更简单。

另外,由于您使用的是PropagateCompletion,因此无需明确调用outputBlock.Complete()