BufferBlock.ReceiveAsync зависает в HostedService - PullRequest
0 голосов
/ 08 января 2019

Я пытаюсь использовать 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, но должна быть какая-то тонкость, которую я пропускаю.

1 Ответ

0 голосов
/ 08 января 2019

Проблема заключалась в том, что мой контейнер IoC подключал несколько экземпляров IEmailService, а экземпляр, который вызывал ReceiveAsync, отличался от экземпляра, который вызывал Post. Это потому, что EmailService был экземпляром IEmailService и IHostedService.

Ответ должен был полностью отказаться от IEmailService. Чтобы использовать EmailService в производственном коде, я могу внедрить экземпляр IEnumerable<IHostedService>, а затем извлечь свой EmailService из этой коллекции, используя OfType<EmailService>().First().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...