预告片:伙计们,这个问题不是关于如何实施重试政策。这是关于正确完成TPL数据流块的。
这个问题大部分是我上一个问题Retry policy within ITargetBlock的延续。这个问题的答案是@ svick使用TransformBlock
(来源)和TransformManyBlock
(目标)的智能解决方案。剩下的唯一问题是以正确的方式完成此块:等待所有重试先完成,然后完成目标块。这是我最终得到的结果(它只是一个片段,不要过多关注非线程安全的retries
集):
var retries = new HashSet<RetryingMessage<TInput>>();
TransformManyBlock<RetryableMessage<TInput>, TOutput> target = null;
target = new TransformManyBlock<RetryableMessage<TInput>, TOutput>(
async message =>
{
try
{
var result = new[] { await transform(message.Data) };
retries.Remove(message);
return result;
}
catch (Exception ex)
{
message.Exceptions.Add(ex);
if (message.RetriesRemaining == 0)
{
if (failureHandler != null)
failureHandler(message.Exceptions);
retries.Remove(message);
}
else
{
retries.Add(message);
message.RetriesRemaining--;
Task.Delay(retryDelay)
.ContinueWith(_ => target.Post(message));
}
return null;
}
}, dataflowBlockOptions);
source.LinkTo(target);
source.Completion.ContinueWith(async _ =>
{
while (target.InputCount > 0 || retries.Any())
await Task.Delay(100);
target.Complete();
});
这个想法是执行某种轮询并验证是否仍然有等待处理的消息,并且没有消息需要重试。但在这个解决方案中,我不喜欢轮询的想法。
是的,我可以将添加/删除重试的逻辑封装到一个单独的类中,甚至是当重试集变空时执行一些操作,但是如何处理target.InputCount > 0
条件?当没有该块的待处理消息时,没有这样的回调被调用,因此似乎在一个具有小延迟的循环中验证target.ItemCount
是唯一的选择。
有人知道更聪明的方法吗?
答案 0 :(得分:2)
也许ManualResetEvent可以帮你解决问题。
将公共属性添加到TransformManyBlock
private ManualResetEvent _signal = new ManualResetEvent(false);
public ManualResetEvent Signal { get { return _signal; } }
你走了:
var retries = new HashSet<RetryingMessage<TInput>>();
TransformManyBlock<RetryableMessage<TInput>, TOutput> target = null;
target = new TransformManyBlock<RetryableMessage<TInput>, TOutput>(
async message =>
{
try
{
var result = new[] { await transform(message.Data) };
retries.Remove(message);
// Sets the state of the event to signaled, allowing one or more waiting threads to proceed
if(!retries.Any()) Signal.Set();
return result;
}
catch (Exception ex)
{
message.Exceptions.Add(ex);
if (message.RetriesRemaining == 0)
{
if (failureHandler != null)
failureHandler(message.Exceptions);
retries.Remove(message);
// Sets the state of the event to signaled, allowing one or more waiting threads to proceed
if(!retries.Any()) Signal.Set();
}
else
{
retries.Add(message);
message.RetriesRemaining--;
Task.Delay(retryDelay)
.ContinueWith(_ => target.Post(message));
}
return null;
}
}, dataflowBlockOptions);
source.LinkTo(target);
source.Completion.ContinueWith(async _ =>
{
//Blocks the current thread until the current WaitHandle receives a signal.
target.Signal.WaitOne();
target.Complete();
});
我不确定target.InputCount
的设置位置。因此,在您更改target.InputCount
的位置,您可以添加以下代码:
if(InputCount == 0) Signal.Set();
答案 1 :(得分:1)
结合hwcverwe回答和JamieSee评论可能是理想的解决方案。
首先,您需要创建多个事件:
var signal = new ManualResetEvent(false);
var completedEvent = new ManualResetEvent(false);
然后,您必须创建一个观察者,并订阅TransformManyBlock
,以便在相关事件发生时得到通知:
var observer = new RetryingBlockObserver<TOutput>(completedEvent);
var observable = target.AsObservable();
observable.Subscribe(observer);
观察结果非常简单:
private class RetryingBlockObserver<T> : IObserver<T> {
private ManualResetEvent completedEvent;
public RetryingBlockObserver(ManualResetEvent completedEvent) {
this.completedEvent = completedEvent;
}
public void OnCompleted() {
completedEvent.Set();
}
public void OnError(Exception error) {
//TODO
}
public void OnNext(T value) {
//TODO
}
}
您可以等待信号或完成(所有源项目耗尽)或两者都
source.Completion.ContinueWith(async _ => {
WaitHandle.WaitAll(completedEvent, signal);
// Or WaitHandle.WaitAny, depending on your needs!
target.Complete();
});
您可以检查WaitAll的结果值以了解设置了哪个事件,并做出相应的反应。 您还可以向代码添加其他事件,将它们传递给观察者,以便在需要时设置它们。您可以区分行为,并在出现错误时做出不同的响应,例如