我现在正在使用TPL Dataflow,我需要实现自己的操作块。
此操作块应接受来自两个不同输入块的消息,将这些消息放入单个队列,然后按顺序处理此队列。这里的要点是两个不同的任务不应该同时执行,我不想使用锁。
这是我的解决方案,但它无法正常工作。
public class OrderedActionBlock<TInputLhs, TInputRhs> : IDataflowBlock
where TInputLhs : class
where TInputRhs : class
{
public ITargetBlock<TInputLhs> InputLhs { get { return inputLhs; } }
public ITargetBlock<TInputRhs> InputRhs { get { return inputRhs; } }
private readonly BufferBlock<TInputLhs> inputLhs = new BufferBlock<TInputLhs>();
private readonly BufferBlock<TInputRhs> inputRhs = new BufferBlock<TInputRhs>();
private ITargetBlock<object> queue;
public OrderedActionBlock(Action<TInputLhs> actionLhs, Action<TInputRhs> actionRhs)
{
queue = new ActionBlock<object>(x =>
{
if (x is TInputLhs)
{
actionLhs(x as TInputLhs);
}
else
{
actionRhs(x as TInputRhs);
}
});
inputLhs.LinkTo(queue, new DataflowLinkOptions() { PropagateCompletion = true });
inputRhs.LinkTo(queue, new DataflowLinkOptions() { PropagateCompletion = true });
}
public void Complete()
{
queue.Complete();
}
public Task Completion
{
get { return queue.Completion; }
}
public void Fault(Exception exception)
{
queue.Fault(exception);
}
}
简单用法示例:
static void Main(string[] args)
{
var splitBlock = new SplitBlock<string>(new Predicate<string>(s => s.Length % 2 == 0));
var batchBlock = new BatchBlock<string>(3);
var processInOrderBlock = new OrderedActionBlock<string, string[]>(
new Action<string>((str) =>
{
Console.WriteLine(str);
}),
new Action<string[]>((batch) =>
{
Console.WriteLine("BATCH - " + string.Join(", ", batch));
}));
splitBlock.SourceFiltered.LinkTo(processInOrderBlock.InputLhs, new DataflowLinkOptions() { PropagateCompletion = true });
splitBlock.SourceNonFiltered.LinkTo(batchBlock, new DataflowLinkOptions() { PropagateCompletion = true });
batchBlock.LinkTo(processInOrderBlock.InputRhs, new DataflowLinkOptions() { PropagateCompletion = true });
for (int i = 1; i <= 10; i++)
{
splitBlock.Post(new string(Enumerable.Repeat('x', i).ToArray()));
}
splitBlock.Complete();
processInOrderBlock.Completion.Wait();
return;
}
输出:
xx
xxxx
xxxxxx
xxxxxxxx
xxxxxxxxxx
BATCH - x, xxx, xxxxx
Press any key to continue . . .
看起来消息卡在batchBlock
中。我不知道为什么。
答案 0 :(得分:2)
queue
或inputLhs
的任何完成时(如果在链接期间使用inputRhs
选项),PropagateCompletion = true
看似已完整。
所以,我们需要改变这个:
inputLhs.LinkTo(queue, new DataflowLinkOptions() { PropagateCompletion = true });
inputRhs.LinkTo(queue, new DataflowLinkOptions() { PropagateCompletion = true });
到此:
Task.WhenAll(InputLhs.Completion, InputRhs.Completion)
.ContinueWith(_ => queue.Complete());
答案 1 :(得分:1)
您可能有一个ActionBlock
,它接受带有两个值的ValueTuple
,外加一个索引以指示两个值中的哪个是有效值:
var block = new ActionBlock<(int, Type1, Type2)>(entry =>
{
var (index, item1, item2) = entry;
switch (index)
{
case 1: DoSomething1(item1); break;
case 2: DoSomething2(item2); break;
default: throw new NotImplementedException();
}
});
block.Post((1, someValue1, default));
block.Post((2, default, someValue2));
这样,通过除去两个中间BufferBlock
,可以确保处理的顺序与发布的顺序完全相同。
要使其更美观,更不易出错,您可以创建类似于OrderedActionBlock
的类,但要使用“ fake” ITargetBlock<TInputLhs>
和ITargetBlock<TInputRhs>
属性,它们不是真正的块,而仅仅是传播者门面到单个ActionBlock
。从一个ITargetBlock
到另一个的转换有些棘手,但这是可行的。下面是一个通用的实现。 ActionBlock<TInput1, TInput2>
和Target1
均同时完成时,因此从链接源进行的传播完成应按预期工作。
Target2