[英]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
的构造函数时, Result
的IsFaulted
才设置为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.