Я пытаюсь использовать IHostedService
в качестве отправителя электронной почты с ошибкой и забывчивостью в приложении ASP.NET Core. Похоже, лучший способ сделать это - использовать класс BufferBlock
; проблема в том, что ReceiveAsync
никогда не заканчивается, даже когда я публикую новые элементы в BufferBlock
.
Вот базовый класс HostedService
:
public abstract class HostedService
{
private Task _executingTask;
private CancellationTokenSource _cts;
public Task StartAsync(CancellationToken cancellationToken)
{
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_executingTask = ExecuteAsync(_cts.Token);
return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
if (_executingTask == null)
{
return;
}
_cts.Cancel();
await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));
cancellationToken.ThrowIfCancellationRequested();
}
protected abstract Task ExecuteAsync(CancellationToken cancellationToken);
}
, из которого моя EmailService
получена следующим образом:
public sealed class EmailService : HostedService, IEmailService
{
private readonly ISendEmail _emailClient;
private readonly BufferBlock<MailMessage> _emailQueue;
public EmailService(ISendEmail emailClient)
{
_emailClient = emailClient;
_emailQueue = new BufferBlock<MailMessage>();
}
public void EnqueueEmail(MailMessage email)
{
var accepted = _emailQueue.Post(email);
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var nextEmail = await _emailQueue.ReceiveAsync(cancellationToken).ConfigureAwait(false);
await _emailClient.SendMailAsync(nextEmail);
}
}
}
Интерфейс IEmailService
- это простой метод «забей и забудь»:
public interface IEmailService : IHostedService
{
void EnqueueEmail(MailMessage email);
}
Так что этого должно быть достаточно. В моих контроллерах я должен иметь возможность вводить IEmailService
, а затем ставить сообщения в очередь по мере необходимости. Проблема в том, что когда я запускаю следующий тест:
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
[InlineData(10)]
public async Task Emails_are_sent_after_they_are_enqueued(int emailCount)
{
for (var i = 0; i < emailCount; ++i)
{
_emailService.EnqueueEmail(new MailMessage());
}
await _testEmailClient.WaitForEmailsToBeSentAsync(emailCount);
}
метод ReceiveAsync
никогда не завершается. Я пытался использовать ConfigureAwait(false)
, но это, похоже, не имеет никакого эффекта.
В моих тестах HostedService
запускается конвейером ASP.NET Core и вводится ExecuteAsync
. Я ожидал бы, что ReceiveAsync
завершится, когда элемент будет доступен в BufferBlock
, но должна быть какая-то тонкость, которую я пропускаю.