数据处理管道和瞬态故障处理似乎是相辅相成的,所以我有兴趣了解我是否可以分别获得两个最佳库-TPL Dataflow和Polly-一起玩很开心。
作为起点,我想将故障处理策略应用于ActionBlock
。理想情况下,我想将其封装在具有如下签名的块创建方法中:
ITargetBlock<T> CreatePollyBlock<T>(
Action<T> act, ExecutionDataflowBlockOptions opts, Polly.Policy policy)
从policy.Execute
内部简单ActionBlock
进行操作就很容易了,但是我有以下两个要求:
ExecutionDataflowBlockOptions.MaxDegreeOfParallelism
,我不希望等待重试的项目成为“数”。为了满足这些要求,我认为我需要一个“内部” ActionBlock
,并应用用户提供的ExecutionDataflowBlockOptions
,并且需要一个“外部”块将项目发布到内部块并进行任何等待-然后重试逻辑(或任何策略指示的内容),外部内部块的上下文。这是我的第一次尝试:
// wrapper that provides a data item with mechanism to await completion
public class WorkItem<T>
{
private readonly TaskCompletionSource<byte> _tcs = new TaskCompletionSource<byte>();
public T Data { get; set; }
public Task Completion => _tcs.Task;
public void SetCompleted() => _tcs.SetResult(0);
public void SetFailed(Exception ex) => _tcs.SetException(ex);
}
ITargetBlock<T> CreatePollyBlock<T>(Action<T> act, Policy policy, ExecutionDataflowBlockOptions opts) {
// create a block that marks WorkItems completed, and allows
// items to fault without faulting the entire block.
var innerBlock = new ActionBlock<WorkItem<T>>(wi => {
try {
act(wi.Data);
wi.SetCompleted();
}
catch (Exception ex) {
wi.SetFailed(ex);
}
}, opts);
return new ActionBlock<T>(async x => {
await policy.ExecuteAsync(async () => {
var workItem = new WorkItem<T> { Data = x };
await innerBlock.SendAsync(workItem);
await workItem.Completion;
});
});
}
为了测试它,我创建了一个带有wait-and-retry policy的块和一个虚拟方法,该方法在调用它的前3次(应用程序范围内)均引发异常。然后我输入了一些数据:
"a", "b", "c", "d", "e", "f"
我希望a,b和c失败并退到行尾。但是我观察到它们按以下顺序击中了内块的动作:
"a", "a", "a", "a", "b", "c", "d", "e", "f"
从本质上讲,我未能满足自己的要求,并且很容易理解为什么:在当前项目的所有重试发生之前,外部模块不允许新项目进入。一个简单但看似骇人听闻的解决方案是在外部块中添加一个大的MaxDegreeOfParallelism
值:
return new ActionBlock<T>(async x => {
await policy.ExecuteAsync(async () => {
var workItem = new WorkItem<T> { Data = x };
await innerBlock.SendAsync(workItem);
await workItem.Completion;
});
}, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 100 });
通过此更改,我观察到实际上有新项目在重试之前进入,但是我也释放了一些混乱。尽管前三项始终在末尾,但进入内部块的内容却是随机的:
"a", "e", "b", "c", "d", "a", "e", "b"
所以这要好一些。但理想情况下,我希望保留订单:
"a", "b", "c", "d", "e", "a", "b", "c"
这就是我要坚持的地方,对此进行推理,我想知道在这些约束下是否有可能,特别是CreatePollyBlock
的内部可以执行但不能定义它。例如,如果这些内部组件可以提供重试lambda,我认为这将为我提供更多选择。但这是定义策略的一部分,按照这种设计,我无法做到这一点。
在此先感谢您的帮助。