簡體   English   中英

具有延遲源/數據流的TPL DataFlow

[英]TPL DataFlow with Lazy Source / stream of data

假設您具有配置了並行性的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條記錄在內存中。 OK:如果附加到該數據源的塊將使它們在內存中保留一段時間,則還可以,但是您有機會僅獲取數據的子集(/流)。


一些背景信息:

我打算將此功能與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;
}

上面的示例不是很好,因為我添加了所有項目,卻不知道它們是否實際上已被轉換塊“使用”。 另外,我也不在乎輸出緩沖區。 我知道我需要將其發送到某個地方,以便我可以等待完成,但是此后我沒有使用緩沖區。 所以它應該忘記所有得到的...

如果目標已滿且沒有阻塞,則Post()將返回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;
}

看來您想以定義的並行度( 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用作基本功能,然后在頂部添加其他限制和限制。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM