[英]Why does HttpClient continue to fail during subsequent retries using Polly?
[英]Using Polly with async method does not continue with await
我有以下方法调用异步方法以获取打开的 MailKit 连接:
public async Task Process(string[] userEmails, IEmailMessage emailMessage) {
var smtpClient = await _smtpClient.GetOpenConnection();
_logger.Information("Breaking users into groups and sending out e-mails");
// More Code
}
GetOpenConnection
方法如下所示:
public async Task<IMailTransport> GetOpenConnection() {
var policy = Policy.Handle<Exception>()
.WaitAndRetryForeverAsync(_ => TimeSpan.FromSeconds(60),
(exception, _) => _logger.Error(exception, "Could not establish connection"));
return await policy.ExecuteAsync(EstablishConnection);
}
private async Task<IMailTransport> EstablishConnection() {
var smtpConfiguration = _smtpConfiguration.GetConfiguration();
_logger.Information("Get an open connection for SMTP client");
_smtpClient.Connect(smtpConfiguration.Network.Host, smtpConfiguration.Network.Port,
SecureSocketOptions.None, CancellationToken.None);
var smtpClient = _smtpClient.GetSmtpClient();
return await Task.FromResult(smtpClient);
}
我关闭了我的测试 SMTP 服务器 (smtp4dev) 并按预期连接失败。 Polly 尽职尽责地每 60 秒重试一次连接。 但是,当我重新打开测试 SMTP 服务器时,代码不会继续在 Process 方法中等待它的位置,我无法弄清楚我做错了什么。
如果我将GetOpenConnection
转换为同步方法,一切都会正常工作,但显然,代码执行会被阻塞,直到连接返回。
任何帮助表示赞赏。
更新:
另外需要注意的一点是,如果在获取连接时没有错误,代码会正确执行。 如果我们第一次可以成功抓取连接,那么它将移动到Process
方法中的_logger
行并执行代码的rest。 当我们无法获取连接并且必须重试执行时,它无法移动到_logger
行。
更新 2:
将Connect
方法更改为以下内容:
public async Task Connect(string host, int port = 0, SecureSocketOptions options = SecureSocketOptions.Auto,
CancellationToken cancellationToken = default) {
await _smtpClient.ConnectAsync(host, port, options, cancellationToken);
}
将EstablishConnection
连接更新为:
private async Task<IMailTransport> EstablishConnection() {
var smtpConfiguration = _smtpConfiguration.GetConfiguration();
_logger.Information("Get an open connection for SMTP client");
await _smtpClient.Connect(smtpConfiguration.Network.Host, smtpConfiguration.Network.Port,
SecureSocketOptions.None, CancellationToken.None);
var smtpClient = _smtpClient.GetSmtpClient();
_logger.Information("Got an open connection for SMTP client");
return await Task.FromResult(smtpClient);
}
当我这样做时,现在 Polly 似乎根本没有重试连接。
更新 3:
public void OnPostInsert(PostInsertEvent @event) {
_logger.Information("OnPostInsert");
_ = DispatchEvents(@event.Entity)
.ContinueWith(x => _logger.Error(x.Exception,
"An error occurred while dispatching the insert event"),
TaskContinuationOptions.OnlyOnFaulted);
}
private async Task DispatchEvents(object domainEntity) {
Ensure.That(domainEntity, nameof(domainEntity)).IsNotNull();
_logger.Information("Dispatch domain events");
var entityBaseType = domainEntity.GetType().BaseType;
if (entityBaseType is not { IsGenericType: true }) return;
if (entityBaseType.GetGenericTypeDefinition() != typeof(EntityBase<>))
return;
if (domainEntity.GetType().GetProperty("DomainEvents")?.GetValue(domainEntity, null) is not
IReadOnlyList<INotification> domainEvents) { return; }
foreach (var domainEvent in domainEvents) {
_logger.Information("Publishing Event {@DomainEvent}", domainEvent);
await _mediator.Publish(domainEvent, CancellationToken.None);
}
}
问题不在于 MailKit 或 Polly。 正如上面的一位评论者所指出的那样,并且通过我自己的测试,Polly 不是问题,并且像宣传的那样工作。 我下载了 MailKit,编译了一个调试版本,删除了发布版本,并添加了对新库的引用。
经过调试,我发现MailKit也可以工作并建立了连接。 在调试时,我随机想到问题可能是从我的异步调用中删除了ConfigureAwait(false)
,果然,确实如此。
从某种意义上说,我为自己制造了这个问题,因为我故意将其从异步调用中排除。 我在使用 Serilog 时遇到了问题,我发现我在异步调用中丢失了 ExecutionContext,因此,Serilog 无法捕获 CorrelationId 和丰富器添加到原始线程的其他属性。 因为 ExecutionContext 没有被转移,所以 SecurityContext 也没有被转移,所以再见了 UserPrincipal。 删除ConfigureAwait(false)
似乎消除了这个问题。
一旦我将ConfigureAwait(false)
添加到异步调用中,魔术就开始发生了。 老实说,我对 async 没有完全了解,无法确定为什么删除此方法会导致这些问题。 我知道ConfigureAwait(false)
可以提高性能,因为排队回调以同步上下文并可能避免通过此同步创建的任何死锁是有成本的。
现在我已经确定了问题的根源,我只需要弄清楚如何传输 ExecutionContext(SecurityContext 等)以流向下异步调用。
Process
:
public async Task Process(string[] userEmails, IEmailMessage emailMessage) {
var smtpClient = await _smtpClient.GetOpenConnection().ConfigureAwait(false);
_logger.Information("Breaking users into groups and sending out e-mails");
// ReSharper disable once ForCanBeConvertedToForeach
foreach (var addressGroup in userEmails.BreakArrays(_configuration.EmailGroupSize.Size))
await _sender.SendEmailMessage(addressGroup, smtpClient, emailMessage);
_logger.Information("Emails sent");
await smtpClient.DisconnectAsync(true);
smtpClient.Dispose();
}
GetOpenConnection
:
public async Task<IMailTransport> GetOpenConnection() {
var smtpConfiguration = _smtpConfiguration.GetConfiguration();
var policy = Policy.Handle<Exception>()
.WaitAndRetryForeverAsync(_ => TimeSpan.FromSeconds(60),
(exception, _) => _logger.Error(exception, "Could not establish connection"));
await policy.ExecuteAsync(async () => await _smtpClientAdapter.ConnectAsync(smtpConfiguration.Network.Host,
smtpConfiguration.Network.Port,
SecureSocketOptions.None, CancellationToken.None).ConfigureAwait(false)).ConfigureAwait(false);
return await Task.FromResult(_smtpClientAdapter.GetSmtpClient());
}
MailKitSmtpClientAdapter
class 上的ConnectAsync
:
public async Task ConnectAsync(string host, int port = 0, SecureSocketOptions options = SecureSocketOptions.Auto,
CancellationToken cancellationToken = default) {
await _smtpClient.ConnectAsync(host, port, options, cancellationToken).ConfigureAwait(false);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.