[英]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.