简体   繁体   English

结果<t> - 如果结果出错,抛出异常以触发 Polly 重试</t>

[英]Result<T> - if result has faulted, throw an exception in order to trigger Polly retries

I recently found out about a nuget called LanguageExt.Core and why it is not so efficient to throw exceptions while handling them via middleware or so.我最近发现了一个名为LanguageExt.Core的 nuget,以及为什么在通过中间件等处理异常时抛出异常效率不高。

Speaking of it, I would like to know what is the best way to simply check if the result has faulted, so I can throw the exception in order to trigger Polly's retry pattern logic.说到这里,我想知道简单检查结果是否出错的最佳方法是什么,这样我就可以抛出异常以触发 Polly 的重试模式逻辑。

The best I could think of:我能想到的最好的:

private async Task RunAsync(Uri uri)
{
    await ConnectAsync(uri);

    var result = await ConnectAsync(uri);

    if (result.IsFaulted)
    {
        // Cannot access result.Exception because it's internal
    }

    _ = result.IfFail(ex =>
    {
        _logger.LogError(ex, "Error while connecting to the endpoint");
        throw ex;
    });

    ...
private async Task<Result<Unit>> ConnectAsync(Uri uri)
{
    if (_ws is not null)
    {
        return new Result<Unit>(new InvalidOperationException("The websocket client is already connected"));
    }

    var ws = Options.ClientFactory();
    try
    {
        using var connectTimeout = new CancellationTokenSource(Options.Timeout);
        await ws.ConnectAsync(uri, connectTimeout.Token).ConfigureAwait(false);
    }
    catch
    {
        ws.Dispose();
        return new Result<Unit>(new InvalidOperationException("Failed to connect to the endpoint"));
    }

    await _connectedEvent.InvokeAsync(new ConnectedEventArgs(uri));

    _ws = ws;
    IsRunning = true;

    return Unit.Default;
}

You can just handle a result with IsFaulted == true and not throw the exceptions yourself.您可以只使用IsFaulted == true处理结果,而不是自己抛出异常。

This is a snippet from one of my apps:这是我的一个应用程序的片段:

this.policy = Policy
  .TimeoutAsync(ctx => ((MyContext)ctx).Timeout)
  .WrapAsync(Policy
    .HandleResult<Result<string>>(result => result.IsFaulted)
    .WaitAndRetryForeverAsync( // Will get stopped by the total timeout. Fits as many retries as possible.
      (retry, _) => TimeSpan.FromSeconds(retry / 2d),
      (result, retry, wait, _) => result.Result.IfFail(
        exception => this.logger.Error(exception ?? result.Exception, "Error! {Retry}, {Wait}", retry, wait)
      )
    )
  );
var policyResult = await this.policy.ExecuteAndCaptureAsync(
  async (_, ct1) => await Prelude
    .TryAsync(this.ConnectAsync(ct1))
    .MapAsync(async _ => await this.SendAsync(mimeMessage, ct1))
    .Invoke(),
  new MyContext(timeout),
  ct
);

return policyResult.Result.Match(
  _ => policyResult.Outcome == OutcomeType.Failure
    ? new InfrastructureException("Error!", policyResult.FinalException).ToResult<Unit>()
    : Unit.Default,
  e => new InfrastructureException("Error!", e).ToResult<Unit>()
);
public static class LangExtExtensions
{
    [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static TryAsync<T> TryAsync<T>(this Task<T> self) => Prelude.TryAsync(self);

    [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static TryAsync<T> TryAsyncSucc<T>(this T self) => Prelude.TryAsyncSucc(self);

    [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static TryAsync<T> TryAsyncFail<T>(this Exception self) => Prelude.TryAsyncFail<T>(self);

    [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Try<T> TrySucc<T>(this T self) => Prelude.TrySucc(self);

    [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Try<T> TryFail<T>(this Exception self) => Prelude.TryFail<T>(self);

    [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Result<T> ToResult<T>(this Exception self) => new(self);
}

Hope this helps a litte希望这会有所帮助

EDIT: some more code编辑:更多代码

You can try something like this:你可以尝试这样的事情:

public ctor()
{
  this.policy = Policy
    // There will be no exceptions thrown outside the TryAsync monad
    // Handling faulted results only is enough
    .HandleResult<Result<string>>(result => result.IsFaulted)
    .WaitAndRetryAsync(
      10,
      (retry, _) => TimeSpan.FromSeconds(retry / 2d),
      (result, retry, wait, _) => result.Result.IfFail(
        exception => this.logger.Error(exception ?? result.Exception, "Error! {Retry}, {Wait}", retry, wait)
      )
    );
}

public Task<Result<Unit>> RetryAsync()
{
  var uri = ...;
  var policyResult = await this.policy.ExecuteAndCaptureAsync(
    async (_, ct1) => await this.ConnectAsync(uri) // Should use CancellationToken!
      .MapAsync(async _ => await this.RunAsync(ct1))
      .Do(result => this.logger.Log(result))
      .Invoke(),
    new Context(),
    ct
  );
  return policyResult.Result.Match(
    _ => policyResult.Outcome == OutcomeType.Failure
      ? new Exception("Retries did not complete successfully", policyResult.FinalException).ToResult<Unit>()
      : Unit.Default,
    e => new Exception("Error even after retries", e).ToResult<Unit>()
  );
}

private Task<string> RunAsync(CancellationToken ct = default)
{
  // Logic here
  return "Success"
}

private TryAsync<Unit> ConnectAsync(Uri uri)
{
  if (_ws is not null)
  {
    return new InvalidOperationException("...").TryAsyncFail<Unit>();
  }
  
  return Prelude
    .TryAsync(async () => {
      var ws = Options.ClientFactory();
      using var connectTimeout = new CancellationTokenSource(Options.Timeout);
      await ws.ConnectAsync(uri, connectTimeout.Token).ConfigureAwait(false);
      return ws;
    })
    .Do(async ws => {
      await _connectedEvent.InvokeAsync(new ConnectedEventArgs(uri));
      _ws = ws;
      IsRunning = true;
    })
    .Map(_ => Unit.Default);
}

// The _ws is long-lived so you can move dispose logic to here
// and let service lifetime handle that for you.
// You might want to add checks for being connected to the code above too
public async ValueTask DisposeAsync()
{
  if(_ws is { IsConnected: true })
  {
    await _ws.DisconnectAsync();
  }
  await _ws?.DisposeAsync();
}

As you can see, there is no throwing and catching of exceptions.如您所见,没有抛出和捕获异常。 Wrapping an exception in a result (and a delegate monad) is ~100x more efficient than throwing.在结果(和委托 monad)中包装异常比抛出效率高 100 倍。 The TryAsync monad lets you write short and declarative code overhead of which is inconsequent compared to classical exception workflow. TryAsync monad 让您可以编写简短的声明性代码,与经典的异常工作流相比,这些代码的开销是微不足道的。

Based on the comments it seems like that ConnectAsync can fail with an InvalidOperationException inside a Result object. In this particular case the Result 's IsFaulted is set to true only if an IOE is passed to the Result 's ctor.根据评论,似乎ConnectAsync可能因Result object 中的InvalidOperationException而失败。在这种特殊情况下,仅当IOE传递给Result的构造函数时, ResultIsFaulted才设置为true

So, if you want to perform a retry then you should define your policy something like this:所以,如果你想执行重试,那么你应该像这样定义你的策略:

var retryPolicy = Policy<Result<Unit>>
    .HandleResult(r => r.IsFaulted)
    .WaitAndRetryAsync(...)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 WCF代理故障,但不会引发异常 - WCF proxy faulted but does not throw an exception 过滤CollectionViewSource产生空结果将抛出异常 - filtering CollectionViewSource yielding empty result will throw exception 调用 Execute() 时抛出异常的 Polly 策略 - Polly policy to throw an exception when Execute() is called Polly 重试不会关闭现有的 HttpClient 调用 - Polly Retries Doesn't Close Existing HttpClient Call Polly 不会抛出一些异常? - Polly won't throw on some exceptions? 我必须处理 Polly 中的异常,我需要将结果集中的数据与 Cosmos 数据库中的数据进行比较,然后重试 - I have to handle an exception in Polly where I need to Compare the data in the result set with the data in Cosmos Database and then Retry 具有功能的Polly重试策略不等待结果 - Polly Retry policy with Function is not waiting for result 如果从不满足结果标准,则策略可以引发异常吗? - Can a policy throw an exception if the result criteria is never met? 当方法返回无效结果时,我应该抛出什么异常 - What exception should I throw when method returns invalid result Polly 回退操作未引发指定的异常。 发生了什么? - Polly fallback action did not throw the specified exception. What happened?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM