假设您具有配置了并行性的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;
}
上面的示例不是很好,因为我添加了所有项目,却不知道它们是否实际上已被转换块“使用”。另外,我也不在乎输出缓冲区。我知道我需要将其发送到某个地方,以便我可以等待完成,但是此后我没有使用缓冲区。所以它应该忘掉所有得到的东西...
答案 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
作为基本动力,然后在顶部添加其他限制和限制。