在TryReceiveAll之后使用OutputAvailableAsync的BufferBlock死锁

时间:2014-08-16 09:58:44

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

在处理an answerthis question时,我写了这段代码:

var buffer = new BufferBlock<object>();
var producer = Task.Run(async () =>
{
    while (true)
    {
        await Task.Delay(TimeSpan.FromMilliseconds(100));
        buffer.Post(null);
        Console.WriteLine("Post " + buffer.Count);
    }
});
var consumer = Task.Run(async () =>
{
    while (await buffer.OutputAvailableAsync())
    {
        IList<object> items;
        buffer.TryReceiveAll(out items);
        Console.WriteLine("TryReceiveAll " + buffer.Count);
    }
});
await Task.WhenAll(consumer, producer);

生产者应该每100毫秒将项目发布到缓冲区,并且消费者应该清除缓冲区中的所有项目并异步地等待显示更多项目。

实际发生的事情是制作人清除所有项目一次,然后再也不会超过OutputAvailableAsync。如果我切换消费者逐个删除项目,它将作为例外工作:

while (await buffer.OutputAvailableAsync())
{
    object item;
    while (buffer.TryReceive(out item)) ;
}

我误解了什么吗?如果没有,问题是什么?

2 个答案:

答案 0 :(得分:10)

这是SourceCore内部使用的BufferBlock中的错误。它的TryReceiveAll方法在_enableOffering时没有打开TryReceive布尔数据成员。这导致从OutputAvailableAsync返回的任务永远不会完成。

这是一个最小的重现:

var buffer = new BufferBlock<object>();
buffer.Post(null);

IList<object> items;
buffer.TryReceiveAll(out items);

var outputAvailableAsync = buffer.OutputAvailableAsync();
buffer.Post(null);

await outputAvailableAsync; // Never completes

我刚刚使用this pull request将其修复到.Net核心存储库中。希望修复程序很快就会在nuget包中找到它。

答案 1 :(得分:1)

唉,它是2015年9月底,尽管i3arnon修复了错误,但在错误修复两天后发布的版本中没有解决:Microsoft TPL Dataflow版本4.5。 24。

然而,IReceivableSourceBlock.TryReceive(...)正常工作。 扩展方法将解决问题。在新版本的TPL Dataflow之后,可以轻松更改扩展方法。

/// <summary>
/// This extension method returns all available items in the IReceivableSourceBlock
/// or an empty sequence if nothing is available. The functin does not wait.
/// </summary>
/// <typeparam name="T">The type of items stored in the IReceivableSourceBlock</typeparam>
/// <param name="buffer">the source where the items should be extracted from </param>
/// <returns>The IList with the received items. Empty if no items were available</returns>
public static IList<T> TryReceiveAllEx<T>(this IReceivableSourceBlock<T> buffer)
{
    /* Microsoft TPL Dataflow version 4.5.24 contains a bug in TryReceiveAll
     * Hence this function uses TryReceive until nothing is available anymore
     * */
    IList<T> receivedItems = new List<T>();
    T receivedItem = default(T);
    while (buffer.TryReceive<T>(out receivedItem))
    {
        receivedItems.Add(receivedItem);
    }
    return receivedItems;
}

用法:

while (await this.bufferBlock.OutputAvailableAsync())
{
    // some data available
    var receivedItems = this.bufferBlock.TryReceiveAllEx();
    if (receivedItems.Any())
    {
        ProcessReceivedItems(bufferBlock);
    }
}