C# 如何使用Polly阻止发布到TPL管道的大量作业
我正在探索如何实现C# 如何使用Polly阻止发布到TPL管道的大量作业,c#,task-parallel-library,tpl-dataflow,polly,C#,Task Parallel Library,Tpl Dataflow,Polly,我正在探索如何实现TPL数据流管道的不同方法。请按照代码示例和所有注释理解我的问题。以下是两个简单的模块: //DataClass is very simple class with two properties int Id, and enum Status var downloadBlock = new TransformBlock<DataClass, DataClass>((data) => { //Here I use Polly library to hel
TPL数据流
管道的不同方法。请按照代码示例和所有注释理解我的问题。以下是两个简单的模块:
//DataClass is very simple class with two properties int Id, and enum Status
var downloadBlock = new TransformBlock<DataClass, DataClass>((data) =>
{
//Here I use Polly library to help with retrying when exception has occured
//I have chosen to retry only on WebException, because in this case there is
//no need to try ObjectNotFoundException
var policy = Policy.Handle<WebException>().WaitAndRetry(3, retryAttempt => TimeSpan.FromSeconds(1));
try
{
policy.Execute(() =>
{
//ThisMethodMyThrowWebException();
data.Status = Status.Completed;
});
}
catch (ObjectNotFoundException)
{
data.Status = Status.Failed;
//Notify user that object is not found;
}
catch (WebException we)
{
data.Status = Status.Failed;
//I would like to cancel whole batch of jobs that were sent to pipeline.
}
return data;
}, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 1 });
var actionBlock = new ActionBlock<DataClass>((data) =>
{
if (data.Status != Status.Failed)
{
//Do something with data
}
});
downloadBlock.LinkTo(actionBlock);
//DataClass是一个非常简单的类,有两个属性int Id和enum Status
var downloadBlock=新TransformBlock((数据)=>
{
//在这里,我使用Polly库来帮助在发生异常时重试
//我选择只在WebException上重试,因为在这种情况下
//无需尝试ObjectNotFoundException
var policy=policy.Handle();
尝试
{
policy.Execute(()=>
{
//ThisMethodMyRowWebException();
data.Status=Status.Completed;
});
}
捕获(ObjectNotFoundException)
{
data.Status=Status.Failed;
//通知用户找不到对象;
}
catch(WebException-we)
{
data.Status=Status.Failed;
//我想取消发送到管道的整批作业。
}
返回数据;
},新的ExecutionDataflowBlockOptions{MaxDegreeOfParallelism=1});
var actionBlock=新的actionBlock((数据)=>
{
if(data.Status!=Status.Failed)
{
//处理数据
}
});
downloadBlock.LinkTo(actionBlock);
假设有大量项目被推入管道,并且发生了永久性错误。需要一段时间,所有项目都将通过重试而没有任何成功。相反,我希望在达到某个重试计数阈值时停止处理它们。为了实现这一点,我可以将
CancellationTokenSource
传递给每个块
,但我的目标是创建管道,直到我的应用程序终止。据我所知,如果我取消块
我的管道就消失了。当然,我可以在Execute()
内部将令牌传递给委托,但这只会在处理方法内部停止。关于如何使用Polly library实现这一点,有什么想法吗?在评论部分的帮助下,我得出结论,答案非常简单-我只需将CancellationToken
传递到策略中。执行()
方法如下:
CancellationTokenSource cts = new CancellationTokenSource();
policy.Execute(p =>
{
//ThisMethodMyThrowWebException();
data.Status = Status.Completed;
}, cts.Token);
编辑: 根据其他用户的请求,我正在使用完整的代码示例更新我的答案,该示例尝试回答评论部分中的问题 这有点长,但为了解决
operationCancelleException
问题,我需要拿出逻辑来区分从方法引发的异常(因此应该重试)和由于用户决定取消作业而引发的异常
我还决定在断路器
中加入额外的代码,因为没有它,我认为这个答案是不完整的。我们开始吧
private TransformBlock<ConstructData, ConstructData> _downloadBlock;
private AsyncCircuitBreakerPolicy _circuitBreaker;
//This flag will indicate to circuitBreaker and catch block
//that OperationCanceledException was induced by user and
//therefore it should not be looked upon as worthy by circuitBreaker
//to decide when to open circuit.
private bool _downloadCanceledByUser;
private CancellationTokenSource _downloadCts;
private void CreatePipeline()
{
var failedItemQueue = new Queue<ConstructData>();
_circuitBreaker
= Policy.Handle<WebException>()
.Or<OperationCanceledException>(ex => !_downloadCanceledByUser)
.CircuitBreakerAsync(5, TimeSpan.FromDays(5),
(exception, timespan) => { },
() =>
{
while (failedItemQueue.Count > 0)
{
_downloadBlock.Post(failedItemQueue.Dequeue());
}
});
var retryPolicy = Policy.Handle<WebException>().Or<OperationCanceledException>()
.WaitAndRetryAsync(2, retryAttempt => TimeSpan.FromSeconds(1));
#region DownloadBlock
_downloadBlock = new TransformBlock<ConstructData, ConstructData>(async (construct) =>
{
var downloadPolicy = retryPolicy.WrapAsync(circuitBreaker);
try
{
await downloadPolicy.ExecuteAsync(async fp =>
{
//await MethodThatMayThrowWebException(_downloadCts.Token);
//await MethodThatMayThrowOperationCanceledException(_downloadCts.Token);
construct.Status = DownloadFileStatus.Downloaded;
}, _downloadCts.Token);
}
catch (WebException ex)
{
construct.Status = DownloadFileStatus.Failed;
//Here after 2 failed retries guaranteed by retryPolicy,
//I repost failed item back to the queue.
_downloadBlock.Post(construct);
}
catch (OperationCanceledException)
{
construct.Status = DownloadFileStatus.Canceled;
//In case if OperationCanceledException was thrown from
//MethodThatMayThrowOperationCanceledException()
//item gets posted back to downloadBlock for retry.
if (!_downloadCanceledByUser)
_downloadBlock.Post(construct);
}
//When total exception count reaches circuitBreaker threshold, circuit is left in open state
catch (BrokenCircuitException ex)
{
//TODO Notify user that he needs to check internet connection and press reload.
construct.Status = DownloadFileStatus.Failed;
failedItemQueue.Enqueue(construct);
}
return construct;
},
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 1 });
#endregion DownloadBlock
#region ProcessBlock
var processBlock = new ActionBlock<ConstructData>(construct =>
{
if (construct.Status == DownloadFileStatus.Downloaded)
{
var fullName = string.Concat(construct.Path, construct.Name);
try
{
//ProcessingMethod(_downloadCts.Token);
}
catch (OperationCanceledException ex)
{
//TODO Do logging if needed.
}
}
}, new ExecutionDataflowBlockOptions() { MaxDegreeOfParallelism = 1 });
#endregion
_downloadBlock.LinkTo(processBlock);
}
public void PushDataIntoPipeline(List<ConstructData> data)
{
//Reset properties if previous batch was canceled
if (_downloadCanceledByUser)
{
_downloadCts = new CancellationTokenSource();
_downloadCanceledByUser = false;
}
//Push data into pipeline
_ = data.Select(downloadBlock.SendAsync).ToList();
}
public void CancelData()
{
_downloadCanceledByUser = true;
_downloadCts.Cancel();
}
public void Reload()
{
_circuitBreaker.Reset();
}
private TransformBlock\u downloadBlock;
专用异步断路器策略\u断路器;
//该标志将指示断路器和挡块
//该OperationCanceledException是由用户和
//因此,断路器不应将其视为有价值的
//决定何时开路。
私人bool_下载被用户取消;
private CancellationTokenSource下载;
私有void CreatePipeline()
{
var failedItemQueue=新队列();
_断路器
=Policy.Handle()
.或(ex=>!\u下载用户取消)
.CircuitBreakerAsync(5,时间跨度从天算起(5),
(例外情况,timespan)=>{},
() =>
{
while(failedItemQueue.Count>0)
{
_downloadBlock.Post(failedItemQueue.Dequeue());
}
});
var retryPolicy=Policy.Handle()或()
.WaitAndRetryAsync(2,retrytry=>TimeSpan.FromSeconds(1));
#区域下载块
_downloadBlock=新TransformBlock(异步(构造)=>
{
var downloadPolicy=retryPolicy.WrapAsync(断路器);
尝试
{
等待下载策略。ExecuteAsync(异步fp=>
{
//等待可能通过WebException(_downloadCts.Token)的方法;
//可通过操作取消异常(_downloadCts.Token)等待的方法;
construct.Status=DownloadFileStatus.download;
},_downloadCts.Token);
}
捕获(WebException ex)
{
construct.Status=DownloadFileStatus.Failed;
//在retryPolicy保证的2次失败重试后,
//我将失败的项目重新发布回队列。
_下载block.Post(构造);
}
捕获(操作取消异常)
{
construct.Status=DownloadFileStatus.cancelled;
//如果从中抛出OperationCanceledException
//方法可以通过以下操作取消异常()
//项目被发回downloadBlock以重试。
如果(!\u下载用户取消)
_下载block.Post(构造);
}
//当总异常计数达到断路器阈值时,电路处于断开状态
捕获(断开回路异常除外)
{
//TODO通知用户需要检查internet连接并按重新加载。
construct.Status=DownloadFileStatus.Failed;
failedItemQueue.Enqueue(构造);
}
返回结构;
},
新的ExecutionDataflowBlockOptions{MaxDegreeOfParallelism=1});
#端域下载块
#区域处理块
var processBlock=新操作块(构造=>
{
if(construct.Status==DownloadFileStatus.download)
{
var fullName=string.Concat(construct.Path,construct.Name);
尝试
{
//处理方法(_downloadCts.Token);
}