首页 > 解决方案 > BufferBlock.ReceiveAsync 在 HostedService 中挂起

问题描述

我正在尝试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,但我必须缺少一些线程微妙之处。

标签: c#multithreadingasp.net-coreasync-await

解决方案


问题是我的 IoC 容器正在连接 的多个实例IEmailService,并且正在调用ReceiveAsync的实例与正在调用的实例不同Post。这是因为是和EmailService的一个实例。IEmailServiceIHostedService

答案是IEmailService完全放弃。要EmailService在生产代码中使用,我可以注入一个IEnumerable<IHostedService>实例,然后EmailService使用OfType<EmailService>().First().


推荐阅读