c# - 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
,但我必须缺少一些线程微妙之处。
解决方案
问题是我的 IoC 容器正在连接 的多个实例IEmailService
,并且正在调用ReceiveAsync
的实例与正在调用的实例不同Post
。这是因为是和EmailService
的一个实例。IEmailService
IHostedService
答案是IEmailService
完全放弃。要EmailService
在生产代码中使用,我可以注入一个IEnumerable<IHostedService>
实例,然后EmailService
使用OfType<EmailService>().First()
.