C# 为什么BufferBlock<;T>;。数据可用时,ReceiveAsync()是否挂起?

C# 为什么BufferBlock<;T>;。数据可用时,ReceiveAsync()是否挂起?,c#,task-parallel-library,tpl-dataflow,C#,Task Parallel Library,Tpl Dataflow,我不熟悉TPL数据流 我正在尝试为一个相当快的输入流构建一个节流异步更新。BufferBlock似乎与此非常匹配,因为我可以调用ReceiveAll()来获取缓冲区中的所有内容,有时我可以在ReceiveAsync()上等待任何东西来获取下一个元素 但它有时似乎挂起了ReceiveAsync()调用;失败的条件也很奇怪 请注意我很感兴趣的是为什么会挂起。我已经找到了另一种方法使我正在开发的应用程序工作,但它可能不像我没有使用TPL数据流那样整洁或可扩展,因为我显然不了解它是如何工作的 进一步注意

我不熟悉TPL数据流

我正在尝试为一个相当快的输入流构建一个节流异步更新。BufferBlock似乎与此非常匹配,因为我可以调用ReceiveAll()来获取缓冲区中的所有内容,有时我可以在ReceiveAsync()上等待任何东西来获取下一个元素

但它有时似乎挂起了ReceiveAsync()调用;失败的条件也很奇怪

请注意我很感兴趣的是为什么会挂起。我已经找到了另一种方法使我正在开发的应用程序工作,但它可能不像我没有使用TPL数据流那样整洁或可扩展,因为我显然不了解它是如何工作的

进一步注意这里的关键用法是,我执行一个TryReceiveAll(),如果失败,则等待ReceiveAsync()。这是突发数据到达的常见模式,我希望以批处理的方式处理数据。这就是我不想在ReceiveAsync()上循环的原因,也就是为什么直接挂接ActionBlock或TransformBlock不起作用。如果我删除TryReceiveAll(),我的版本似乎能按预期工作;不过,正如其他评论所指出的,不同的人的行为似乎有所不同,所以这可能是巧合

下面是一个失败的例子。。。将其放入引用System.Threading.Tasks.Dataflow.dll并使用以下命令的控制台应用程序中:

using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
失败的例子:

class Program
{
    static void Main(string[] args)
    {
        var context = new CancellationTokenSource();
        var buffer = new BufferBlock<int>(new DataflowBlockOptions { CancellationToken = context.Token });
        var task = Task.Run(() =>ProcessBuffer(buffer, context.Token), context.Token);

        // shove 10 things onto the buffer
        for(int i = 0; i < 10; i++)
        {
            // shove something on the buffer every second
            buffer.Post(i);
            Thread.Sleep(1000);
        }
        context.Cancel();
    }

    // 1. We expect to see something hit the buffer and go immediately through on the TryReceiveAll
    // 2. Then we should see nothing for a second
    // 3. Then we immediately process the next element from the await ReceiveAsync.
    // 4. We should then repeat 2 & 3 'till i == 10 as there will be nothign for TryReceiveAll.
    static async Task ProcessBuffer(BufferBlock<int> buffer, CancellationToken token)
    {
        try
        {
            while (!token.IsCancellationRequested)
            {
                Console.WriteLine(DateTime.Now.ToShortTimeString() + ": This Breaks it...");
                IList<int> elements;
                if (!buffer.TryReceiveAll(out elements))
                {
                    try
                    {
                        var element = await buffer.ReceiveAsync(TimeSpan.FromMilliseconds(5000), token);
                        elements = new List<int> { element };
                    }
                    catch (TimeoutException) { Console.WriteLine("Failed to get anything on await..."); }
                }
                if (elements != null) Console.WriteLine("Elements: " + string.Join(", ", elements));
            }
        }
        catch (Exception e) { Console.WriteLine("Exception in thread: " + e.ToString()); }
    }
}
……等等

但是如果我改变记录行

Console.WriteLine(DateTime.Now.ToShortTimeString() + ": This Breaks it...");

输出结果与预期一致:

This Works...
Elements: 0
This Works...
Elements: 1
This Works...
Elements: 2
This Works...
等等。 但即使是从控制台复制文本的行为也会将其切换回失败的输出


想法?

你到底想实现什么?您可以删除
ProcessBuffer
,只需将
BufferBlock
链接到
ActionBlock
TransformBlock
;即使有另一种方法可以实现系统目标,我也对其感兴趣。上面的代码是重现问题的示例。这与我正在构建的真实系统无关。至于我在做什么;我有一个入站数据流,我正在缓存并通过缓冲区发送新数据。我有一个单独的任务,然后定期提升所有更新,并通过几个客户端流将它们推出。也许有人是数据流API的专家,可以解释这一点,但在我看来,这就像一个直接的bug,而且是一个奇怪的bug。导致问题的是
DateTime
值的格式化和打印。您可以检索
DateTime.Now
,而不会引起问题,并且可以打印到控制台上长度完全相同的文本。为了进一步混淆这个问题,我发现有时它仍然可以正常工作,如果我不格式化
DateTime
值或不打印它,它也可以正常工作。我怀疑实现
BufferBlock
对象或相关类的人试图变得聪明,试图编写一个奇特的同步实现(例如,无锁、旋转等待等),并引入了一个竞争条件,在某些情况下打破了等待。值得一提的是,如果我通过直接调用
ProcessBuffer()
并在
Task.Run()
中运行
Post()
循环来翻转线程,我根本无法重现问题。我无法使用Tpl.Dataflow的V4.5.24和直接复制并粘贴到.NET4.6.1控制台应用程序中。你有没有可能在某处发布一个完整的例子?
Console.WriteLine("This Works...");
This Works...
Elements: 0
This Works...
Elements: 1
This Works...
Elements: 2
This Works...