C# 如何对IEnumerable的所有元素执行Polly策略,并在第一个未处理的异常时停止
库的策略,例如C# 如何对IEnumerable的所有元素执行Polly策略,并在第一个未处理的异常时停止,c#,async-await,polly,C#,Async Await,Polly,库的策略,例如隔板,重试等,包含一个带有许多重载(18)的方法ExecuteAsync>,但它们都不允许对IEnumerable的所有元素执行策略并收集结果。似乎整个库都专注于执行单个操作的目标,将管理多个执行的责任留给客户机代码。我想通过为所有Polly策略(接口的所有实现)实现一个扩展方法来修复这一遗漏,签名如下: public static Task<TResult[]> ExecuteAsync<TSource, TResult>( this IAsync
隔板
,重试
等,包含一个带有许多重载(18)的方法ExecuteAsync>,但它们都不允许对IEnumerable
的所有元素执行策略并收集结果。似乎整个库都专注于执行单个操作的目标,将管理多个执行的责任留给客户机代码。我想通过为所有Polly策略(接口的所有实现)实现一个扩展方法来修复这一遗漏,签名如下:
public static Task<TResult[]> ExecuteAsync<TSource, TResult>(
this IAsyncPolicy policy,
IEnumerable<TSource> source,
Func<TSource, Task<TResult>> action,
bool continueOnCapturedContext = false,
bool onErrorContinue = false)
onErrorContinue
参数是这个问题最重要的方面,因为它控制策略失败时的行为。我的意图是将此扩展方法用于数千个元素,如果我的保单未预期/处理任何例外情况,我希望立即、优雅地终止整个执行。如果参数onErrorContinue
具有默认值false
,则第一个未处理的异常应导致取消所有挂起的操作,并且在所有启动的操作完成后,整个执行应立即终止。在onErrorContinue:true
的相反情况下,所有元素都应由策略处理。最后,应传播所有异常,并将其捆绑在一个聚合异常中
,独立于onErrorContinue
值
我如何实现这个扩展方法
此方法的假设使用场景:
var policy = Policy
.BulkheadAsync(maxParallelization: 10, maxQueuingActions: Int32.MaxValue)
.WrapAsync(Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(retryCount: 3,
sleepDurationProvider: n => TimeSpan.FromMilliseconds(1000 * n))
);
var urls = Enumerable.Range(1, 1000).Select(n => n.ToString());
var random = new Random(0);
string[] results = await policy.ExecuteAsync(urls, async url =>
{
await Task.Delay(500); // Simulate a web request
lock (random) if (random.NextDouble() < 0.66)
throw new HttpRequestException($"Url #{url} failed");
return url;
}, onErrorContinue: false);
var policy=policy
.BulkheadAsync(maxParallelization:10,maxQueuingActions:Int32.MaxValue)
.WrapAsync(政策)
.Handle()
.WaitAndRetryAsync(retryCount:3,
sleepDurationProvider:n=>TimeSpan.From毫秒(1000*n))
);
var url=Enumerable.Range(11000).Select(n=>n.ToString());
var random=新随机数(0);
string[]results=wait policy.ExecuteAsync(url,异步url=>
{
等待任务。延迟(500);//模拟web请求
锁定(随机)if(random.NextDouble()<0.66)
抛出新的HttpRequestException($“Url{Url}失败”);
返回url;
},继续:false);
这在生产中应该很少发生,但在开发过程中可能经常发生,并且可能会影响生产率。下面是我对ExecuteAsync
方法的实现。CancellationTokenSource
用于在异常情况下取消挂起的操作。当有更重要的异常要传播时,任务.whalll
很好地忽略了操作CanceledException
s。最后是Task.whalll
任务返回时未被wait
ed,以保留所有异常
public static Task<TResult[]> ExecuteAsync<TSource, TResult>(
this IAsyncPolicy policy,
IEnumerable<TSource> source,
Func<TSource, Task<TResult>> action,
bool continueOnCapturedContext = false,
bool onErrorContinue = false)
{
// Arguments validation omitted
var cts = new CancellationTokenSource();
var token = !onErrorContinue ? cts.Token : default;
var tasks = source.Select(async (item) =>
{
try
{
return await policy.ExecuteAsync(async _ =>
{
return await action(item);
}, token, continueOnCapturedContext);
}
catch
{
if (!onErrorContinue) cts.Cancel();
throw;
}
}).ToArray();
var whenAll = Task.WhenAll(tasks);
_ = whenAll.ContinueWith(_ => cts.Dispose(), TaskScheduler.Default);
return whenAll;
}
公共静态任务执行同步(
这项国际同步政策,
IEnumerable来源,
Func action,
bool continueOnCapturedContext=false,
bool onErrorContinue=false)
{
//省略参数验证
var cts=新的CancellationTokenSource();
var token=!onErrorContinue?cts.token:默认值;
var tasks=source.Select(异步(项目)=>
{
尝试
{
return wait policy.ExecuteAsync(异步=>
{
返回等待动作(项目);
},令牌,continueOnCapturedContext);
}
抓住
{
如果(!onErrorContinue)cts.Cancel();
投掷;
}
}).ToArray();
var whenAll=Task.whenAll(任务);
_=whenAll.ContinueWith(=>cts.Dispose(),TaskScheduler.Default);
返回whalll;
}
模拟任务.whalll
行为,这在本例中是可取的,否则(使用async/await)。因此,我很高兴通过使用一个小的ContinueWith
,来避免这个麻烦,以便最终使用取消令牌源代码
提出了一种处理多个异常的替代方法。此解决方案传播嵌套的AggregateException
,这听起来很难听,但实际上这没什么,因为wait
ing异步方法消除了一级嵌套。注意,我问这个问题的目的是。我将推迟24小时发布我的答案,以防有人想在不受已发布解决方案影响的情况下尝试解决此问题。我正在查看和几乎每一个重载都涉及上下文
概念。您的设计中是否故意遗漏了它?@PeterCsala添加对上下文
甚至取消令牌
参数的支持会使实现过于复杂。因此,我要求提供一种只提供基本功能的方法。任何想要更多的人,都至少有一个起点。
public static Task<TResult[]> ExecuteAsync<TSource, TResult>(
this IAsyncPolicy policy,
IEnumerable<TSource> source,
Func<TSource, Task<TResult>> action,
bool continueOnCapturedContext = false,
bool onErrorContinue = false)
{
// Arguments validation omitted
var cts = new CancellationTokenSource();
var token = !onErrorContinue ? cts.Token : default;
var tasks = source.Select(async (item) =>
{
try
{
return await policy.ExecuteAsync(async _ =>
{
return await action(item);
}, token, continueOnCapturedContext);
}
catch
{
if (!onErrorContinue) cts.Cancel();
throw;
}
}).ToArray();
var whenAll = Task.WhenAll(tasks);
_ = whenAll.ContinueWith(_ => cts.Dispose(), TaskScheduler.Default);
return whenAll;
}