Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/327.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何对IEnumerable的所有元素执行Polly策略,并在第一个未处理的异常时停止_C#_Async Await_Polly - Fatal编程技术网

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;
}