C# 如何在TPL数据流中重置延迟/拒绝的消息
我在我的应用程序中使用TDF,到目前为止效果非常好,不幸的是,我偶然发现了一个特定的问题,似乎无法用现有的数据流机制直接处理: 我有N个生产者(在本例中为BufferBlock),它们都只链接到1个(都指向同一个)ActionBlock。此块始终一次处理1个项目,并且只能处理1个项目 对于从生产者到ActionBlock的链接,我还想添加一个过滤器,但这里的特殊情况是,过滤器条件可以独立于已处理的项目进行更改,并且不能丢弃该项目! 因此,基本上我想处理所有项目,但处理项目的顺序/时间可能会发生变化 不幸的是,我了解到,如果一个项目被“拒绝”一次->筛选条件的计算结果为false,并且如果该项目没有传递给另一个块(例如NullTarget),则目标块不会重试同一项目(并且不会重新计算筛选) 正如我们所看到的,ActionBlock从不带过滤器的BufferBlock中获取第一个元素,然后尝试从带过滤器的BufferBlock中获取一个元素。过滤器的计算结果为false,它将继续从没有过滤器的块中获取所有元素C# 如何在TPL数据流中重置延迟/拒绝的消息,c#,task-parallel-library,pipeline,tpl-dataflow,C#,Task Parallel Library,Pipeline,Tpl Dataflow,我在我的应用程序中使用TDF,到目前为止效果非常好,不幸的是,我偶然发现了一个特定的问题,似乎无法用现有的数据流机制直接处理: 我有N个生产者(在本例中为BufferBlock),它们都只链接到1个(都指向同一个)ActionBlock。此块始终一次处理1个项目,并且只能处理1个项目 对于从生产者到ActionBlock的链接,我还想添加一个过滤器,但这里的特殊情况是,过滤器条件可以独立于已处理的项目进行更改,并且不能丢弃该项目! 因此,基本上我想处理所有项目,但处理项目的顺序/时间可能会发生变
public class ConsumeTest
{
private readonly BufferBlock<int> m_bufferBlock1;
private readonly BufferBlock<int> m_bufferBlock2;
private readonly ActionBlock<int> m_actionBlock;
public ConsumeTest()
{
m_bufferBlock1 = new BufferBlock<int>();
m_bufferBlock2 = new BufferBlock<int>();
var options = new ExecutionDataflowBlockOptions() { BoundedCapacity = 1, MaxDegreeOfParallelism = 1 };
m_actionBlock = new ActionBlock<int>((item) => BlockAction(item), options);
var start = DateTime.Now;
var elapsed = TimeSpan.FromMinutes(1);
m_bufferBlock1.LinkTo(m_actionBlock, x => IsTimeElapsed(start, elapsed));
m_bufferBlock2.LinkTo(m_actionBlock);
FillBuffers();
}
private void BlockAction(int item)
{
Console.WriteLine(item);
Thread.Sleep(2000);
}
private void FillBuffers()
{
for (int i = 0; i < 1000; i++)
{
if (i % 2 == 0)
{
m_bufferBlock1.Post(i);
}
else
{
m_bufferBlock2.Post(i);
}
}
}
private bool IsTimeElapsed(DateTime start, TimeSpan elapsed)
{
Console.WriteLine("checking time elapsed");
return DateTime.Now > (start + elapsed);
}
public async Task Start()
{
await m_actionBlock.Completion;
}
}
我的期望是,在处理了不带过滤器的BufferBlock中的一个元素之后,它会尝试再次从另一个带过滤器的BufferBlock中获取该元素,并再次对其进行求值
这将是我预期(或期望)的结果:
我现在的问题是,是否有一种方法可以“重置”已经“拒绝”的消息,以便再次对其进行评估,或者是否有另一种方法通过对其进行不同的建模?概括地说,它们是否真正从两个缓冲区中拉出并不重要!(因为我知道这取决于调度,如果同一个块中的两个项目不时地退出队列,这是完全正确的)
但重要的是,“拒绝”消息不能被丢弃或重新排队,因为一个缓冲区内的顺序很重要
提前感谢一个想法是定期或按需刷新两个块之间的链接。实现定期刷新并不十分困难。下面是一个实现:
public static IDisposable LinkTo<TOutput>(this ISourceBlock<TOutput> source,
ITargetBlock<TOutput> target, Predicate<TOutput> predicate,
TimeSpan refreshInterval, DataflowLinkOptions linkOptions = null)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (target == null) throw new ArgumentNullException(nameof(target));
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
if (refreshInterval < TimeSpan.Zero)
throw new ArgumentOutOfRangeException(nameof(refreshInterval));
linkOptions = linkOptions ?? new DataflowLinkOptions();
var locker = new object();
var cts = new CancellationTokenSource();
var token = cts.Token;
var currentLink = source.LinkTo(target, linkOptions, predicate);
var loopTask = Task.Run(async () =>
{
try
{
while (true)
{
await Task.Delay(refreshInterval, token).ConfigureAwait(false);
currentLink.Dispose();
currentLink = source.LinkTo(target, linkOptions, predicate);
}
}
finally
{
lock (locker) { cts.Dispose(); cts = null; }
}
}, token);
_ = Task.Factory.ContinueWhenAny(new[] { source.Completion, target.Completion },
_ => { lock (locker) cts?.Cancel(); }, token, TaskContinuationOptions.None,
TaskScheduler.Default);
return new Unlinker(() =>
{
lock (locker) cts?.Cancel();
// Wait synchronously the task to complete, ignoring cancellation exceptions.
try { loopTask.GetAwaiter().GetResult(); } catch (OperationCanceledException) { }
currentLink.Dispose();
});
}
private struct Unlinker : IDisposable
{
private readonly Action _action;
public Unlinker(Action disposeAction) => _action = disposeAction;
void IDisposable.Dispose() => _action?.Invoke();
}
m_bufferBlock1
和m_actionBlock
之间的链接将每10秒刷新一次,直到两个块中的一个块完成
1
checking time elapsed
3
checking time elapsed
5
checking time elapsed
7
checking time elapsed
9
checking time elapsed
11
checking time elapsed
13
checking time elapsed
15
// after timer has elapsed take elements also from other buffer
2
17
4
19
public static IDisposable LinkTo<TOutput>(this ISourceBlock<TOutput> source,
ITargetBlock<TOutput> target, Predicate<TOutput> predicate,
TimeSpan refreshInterval, DataflowLinkOptions linkOptions = null)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (target == null) throw new ArgumentNullException(nameof(target));
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
if (refreshInterval < TimeSpan.Zero)
throw new ArgumentOutOfRangeException(nameof(refreshInterval));
linkOptions = linkOptions ?? new DataflowLinkOptions();
var locker = new object();
var cts = new CancellationTokenSource();
var token = cts.Token;
var currentLink = source.LinkTo(target, linkOptions, predicate);
var loopTask = Task.Run(async () =>
{
try
{
while (true)
{
await Task.Delay(refreshInterval, token).ConfigureAwait(false);
currentLink.Dispose();
currentLink = source.LinkTo(target, linkOptions, predicate);
}
}
finally
{
lock (locker) { cts.Dispose(); cts = null; }
}
}, token);
_ = Task.Factory.ContinueWhenAny(new[] { source.Completion, target.Completion },
_ => { lock (locker) cts?.Cancel(); }, token, TaskContinuationOptions.None,
TaskScheduler.Default);
return new Unlinker(() =>
{
lock (locker) cts?.Cancel();
// Wait synchronously the task to complete, ignoring cancellation exceptions.
try { loopTask.GetAwaiter().GetResult(); } catch (OperationCanceledException) { }
currentLink.Dispose();
});
}
private struct Unlinker : IDisposable
{
private readonly Action _action;
public Unlinker(Action disposeAction) => _action = disposeAction;
void IDisposable.Dispose() => _action?.Invoke();
}
m_bufferBlock1.LinkTo(m_actionBlock, x => IsTimeElapsed(start, elapsed),
refreshInterval: TimeSpan.FromSeconds(10));