【发布时间】:2021-12-03 13:39:34
【问题描述】:
我有一个 .NET BackgroundService 用于使用 BlockingCollection<Notification> 管理通知。
我的实现导致 CPU 使用率过高,尽管 BlockingCollection 处理的工作并不多。
我收集了一些转储,似乎我遇到了线程池饥饿。
我不确定应该如何重构以避免这种情况。
private readonly BlockingCollection<Notification> _notifications;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
Task.Run(async () =>
{
await _notificationsContext.Database.MigrateAsync(stoppingToken);
while (!stoppingToken.IsCancellationRequested)
{
foreach (var notification in _notifications.GetConsumingEnumerable(stoppingToken))
{
// process notification
}
}
}, stoppingToken);
}
我也尝试删除 while 循环,但问题仍然存在。
编辑:添加了制作人
public abstract class CommandHandlerBase
{
private readonly BlockingCollection<Notification> _notifications;
public CommandHandlerBase(BlockingCollection<Notification> notifications)
{
_notifications = notifications;
}
protected void EnqueueNotification(AlertImapact alertImapact,
AlertUrgency alertUrgency,
AlertSeverity alertServerity,
string accountName,
string summary,
string details,
bool isEnabled,
Exception exception,
CancellationToken cancellationToken = default)
{
var notification = new Notification(accountName, summary, details, DateTime.UtcNow, exception.GetType().ToString())
{
Imapact = alertImapact,
Urgency = alertUrgency,
Severity = alertServerity,
IsSilenced = !isEnabled,
};
_notifications.Add(notification, cancellationToken);
}
}
【问题讨论】:
-
我将从完全删除
Task.Run(async () =>部分开始。虽然也不需要循环,但它无论如何都不会做任何事情。 -
您的错误消息显示您正在阻止
GetConsumingEnumerable调用上的线程池线程,这是阻塞的。如果这导致线程池饥饿,我假设您必须并行调用ExecuteAsync多次?永远窃取 1 个线程池线程是不好的(这不是线程池的用途),但要导致饥饿,您必须阻塞许多线程池线程。 -
ExecuteAsync 应该返回一个代表后台服务生命周期的任务,所以如果你必须使用它,至少等待你的 Task.Run..
-
我根本没有调用
ExecuteAsync,我正在使用.NET 辅助服务,运行时正在启动它。我也尝试通过删除Task.Run()调用,但仍然没有改善。 -
为什么不在其中放置一些日志记录/断点,看看它被调用了多少次?
标签: c# blockingcollection .net-6.0 worker-service