具有延迟源/数据流的TPL DataFlow

时间:2018-09-28 11:31:09

标签: c# tpl-dataflow

假设您具有配置了并行性的TransformBlock,并且希望通过该块流传输数据。仅当管道可以实际开始处理输入数据时,才应创建输入数据。 (并且应该在离开管道时立即释放。)

我可以实现吗?如果可以的话?

基本上,我想要一个充当迭代器的数据源。 像这样:

public IEnumerable<Guid> GetSourceData()
{
    //In reality -> this should also be an async task -> but yield return does not work in combination with async/await ...
    Func<ICollection<Guid>> GetNextBatch = () => Enumerable.Repeat(100).Select(x => Guid.NewGuid()).ToArray();

    while (true)
    {
        var batch = GetNextBatch();
        if (batch == null || !batch.Any()) break;
        foreach (var guid in batch)
            yield return guid;
    }
}

这将导致+-100条记录在内存中。好的:如果您附加到此数据源的块可以将它们保留在内存中一段时间​​,那么还可以,但是您有机会仅获取数据的子集(/流)。


一些背景信息:

我打算将此功能与azure cosmos db结合使用,其中源可以是集合中的所有对象或变更供稿。不用说,我不希望所有这些对象都存储在内存中。所以这行不通:

using System.Threading.Tasks.Dataflow;

public async Task ExampleTask()
{
    Func<Guid, object> TheActualAction = text => text.ToString();

    var config = new ExecutionDataflowBlockOptions
    {
        BoundedCapacity = 5,
        MaxDegreeOfParallelism = 15
    };
    var throtteler = new TransformBlock<Guid, object>(TheActualAction, config);
    var output = new BufferBlock<object>();
    throtteler.LinkTo(output);

    throtteler.Post(Guid.NewGuid());
    throtteler.Post(Guid.NewGuid());
    throtteler.Post(Guid.NewGuid());
    throtteler.Post(Guid.NewGuid());
    //...
    throtteler.Complete();

    await throtteler.Completion;
}

上面的示例不是很好,因为我添加了所有项目,却不知道它们是否实际上已被转换块“使用”。另外,我也不在乎输出缓冲区。我知道我需要将其发送到某个地方,以便我可以等待完成,但是此后我没有使用缓冲区。所以它应该忘掉所有得到的东西...

2 个答案:

答案 0 :(得分:2)

如果目标已满且没有阻塞,

PostAsync将返回false。尽管可以在忙等待循环中使用此 ,但这很浪费。另一方面,SendAsync将等待目标已满:

public async Task ExampleTask()
{
    var config = new ExecutionDataflowBlockOptions
    {
        BoundedCapacity = 50,
        MaxDegreeOfParallelism = 15
    };
    var block= new ActionBlock<Guid, object>(TheActualAction, config);

    while(//some condition//)
    { 
        var data=await GetDataFromCosmosDB();
        await block.SendAsync(data);
        //Wait a bit if we want to use polling
        await Task.Delay(...);
    }

    block.Complete();
    await block.Completion;
}

答案 1 :(得分:0)

似乎您想以定义的并行度(MaxDegreeOfParallelism = 15)处理数据。对于这样一个简单的要求,TPL数据流非常笨拙。

有一个非常简单而强大的模式可以解决您的问题。这是一个并行的异步foreach循环,如下所述:https://blogs.msdn.microsoft.com/pfxteam/2012/03/05/implementing-a-simple-foreachasync-part-2/

public static Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body) 
{ 
    return Task.WhenAll( 
        from partition in Partitioner.Create(source).GetPartitions(dop) 
        select Task.Run(async delegate { 
            using (partition) 
                while (partition.MoveNext()) 
                    await body(partition.Current); 
        })); 
}

然后您可以编写如下内容:

var dataSource = ...; //some sequence
dataSource.ForEachAsync(15, async item => await ProcessItem(item));

非常简单。

您可以使用SemaphoreSlim动态减少DOP。信号量充当仅允许N个并发线程/任务进入的门。N可以动态更改。

因此,您将使用ForEachAsync作为基本动力,然后在顶部添加其他限制和限制。