我正在尝试在ASP.NET Core应用程序中使用IHostedService
作为即发即弃的电子邮件发件人。看起来最好的方法是使用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
会完成,但是我一定缺少一些线程上的细微之处。
答案 0 :(得分:0)
问题是我的IoC容器正在连接IEmailService
的多个实例,并且正在调用ReceiveAsync
的实例与正在调用Post
的实例不同。这是因为EmailService
是IEmailService
和IHostedService
的实例。
答案是完全抛弃IEmailService
。要在生产代码中使用EmailService
,我可以注入一个IEnumerable<IHostedService>
实例,然后使用EmailService
从该集合中提取我的OfType<EmailService>().First()
。