也许TPL不是正确的工具,但至少从一个不熟悉它的人看来,它似乎应该有我想要的东西。我愿意接受那些不使用它的答案。
给出这样的方法:
public Task Submit(IEnumerable<WorkItem> work)
这可以对项目集合执行昂贵的异步操作。通常,调用者会批量处理这些项目并尽可能多地提交,并且这些批次之间存在相当长的延迟,因此它可以相当有效地执行。
然而,在某些情况下,不会发生外部批处理,Submit
会快速连续多次调用少量项目(通常只有一项),甚至可能同时来自不同的线程。
我想做的是推迟处理(在累积参数的同时),直到有一定的时间没有调用,然后执行整批操作,按原定的顺序。
或者换句话说,每次调用该方法时,它都应该将其参数添加到待处理项列表中,然后从零重新启动延迟,这样在处理任何内容之前都需要一定的空闲时间。
我不希望批量限制(所以我不认为BatchBlock是正确答案),我只想要延迟/超时。我确定调用模式是这样的,将在某个时刻成为空闲时段。
我不确定是否最好推迟第一次通话,或者是否应该立即开始操作,如果操作仍在进行中,则只推迟后续通话。
如果它使问题更容易,我可以使Submit
返回void而不是Task
(即无法在完成时观察)。
我确信我可以混淆一些像这样工作的东西,但它似乎应该已经存在于某个地方。谁能指出我正确的方向? (但我不想使用非核心库。)
答案 0 :(得分:0)
好的,所以由于缺乏找到合适的东西,我最终自己实现了一些东西。似乎可以做到这一点。 (我实际上比实际代码中显示的更加一般,所以我可以更容易地重复使用它,但这说明了这个概念。)
private readonly ConcurrentQueue<WorkItem> _Items
= new ConcurrentQueue<WorkItem>();
private CancellationTokenSource _CancelSource;
public async Task Submit(IEnumerable<WorkItem> items)
{
var cancel = ReplacePreviousTasks();
foreach (var item in items)
{
_Items.Enqueue(item);
}
await Task.Delay(TimeSpan.FromMilliseconds(250), cancel.Token);
if (!cancel.IsCancellationRequested)
{
await RunOperation();
}
}
private CancellationTokenSource ReplacePreviousTasks()
{
var cancel = new CancellationTokenSource();
var old = Interlocked.Exchange(ref _CancelSource, cancel);
if (old != null)
{
old.Cancel();
}
return cancel;
}
private async Task RunOperation()
{
var items = new List<WorkItem>();
WorkItem item;
while (_Items.TryDequeue(out item))
{
items.Add(item);
}
// do the operation on items
}
如果在250ms内发生多次提交,则先前的提交将被取消,并且在250ms启动后对所有项目执行一次操作(从最新提交开始计算)。
如果在操作运行时发生了另一次提交,它将继续运行而不取消(它很可能会从后面的调用中窃取一些项目,但没关系)。
(技术上检查cancel.IsCancellationRequested
并不是必需的,因为上面的await
会在延迟期间被取消时抛出异常。但它没有伤害,并且有一个小窗口它可能会抓住。)