我有一個BackgroundService用于管理通知的 .NET使用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);
}
}
uj5u.com熱心網友回復:
阻塞是昂貴的,但讓執行緒休眠和重新調度更昂貴。為了避免這種情況,.NET 通常在實際阻塞執行緒之前使用SpinWait開始阻塞操作。Spinwait 使用一個核心暫時不做任何事情,這會導致您觀察到的 CPU 使用率。
要解決此問題,請使用像Channels這樣的異步集合。
- 通道允許您向其異步發布或讀取訊息,并保留其順序。
- 它是執行緒安全的,這意味著多個讀者和作者可以同時寫入它。
- 您可以創建一個有界頻道,以防止發布者在頻道已滿時發帖。
- 最后,您可以通過 讀取 Channel 中的所有訊息
IAsyncEnumerable,使處理代碼更容易。
避免使用 Channels 阻塞
在您的情況下,代碼可能會更改為:
private readonly Channel<Notification> _notifications=Channel.CreateUnbounded<Notification>();
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await _notificationsContext.Database.MigrateAsync(stoppingToken);
await foreach(var notification in _notifications.Reader.ReadAllAsync(stoppingToken))
{
// process notification
}
}
通道有意使用單獨的介面進行讀取和寫入。閱讀,您使用ChannelReader通過回傳的類Channel.Reader。寫,您使用ChannelWriter通過回傳的類Channel.Writer。Channel 可以隱式轉換為任一型別,從而可以輕松撰寫僅接受/生成 ChannelReader 或 ChannelWriter 的發布者和訂閱者方法。
要寫入通道,請使用 ChannelWriter 的WriteAsync方法:
await _notifications.Writer.WriteAsync(someNotification);
當您完成撰寫并想要關閉通道時,您需要在撰寫器上呼叫Complete():
await _notification.Writer.Complete();
處理回圈將讀取任何剩余的訊息。要等到它完成,您需要等待ChannelReader.Completion任務:
await _notification.Reader.Completion;
從其他班級發帖
當您使用 BackgroundService 時,通知通常來自其他類。這意味著以某種方式發布者和服務都需要訪問同一個頻道。一種方法是使用輔助類并將其注入發布者和服務中。
該MessageChannel<T>班做這一點,也通過關閉作家處理應用程式終止:
public class MessageChannel<T>:IDisposable
{
private readonly Channel<Envelope<T>> _channel;
public ChannelReader<Envelope<T>> Reader => _channel;
public ChannelWriter<Envelope<T>> Writer => _channel;
public MessageChannel(IHostApplicationLifetime lifetime)
{
_channel = Channel.CreateBounded<Envelope<T>>(1);
lifetime.ApplicationStopping.Register(() => Writer.TryComplete());
}
private readonly CancellationTokenSource _cts = new();
public CancellationToken CancellationToken => _cts.Token;
public void Stop()
{
_cts.Cancel();
}
public void Dispose()
{
_cts.Dispose();
}
}
這可以在后臺服務中注入:
MessageChannel<Notification> _notifications;
ChannelReader<Notification> _reader;
public MyService(MessageChannel<Notification> notifications)
{
_notifications=notifications;
_reader=notifications.Reader;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await _notificationsContext.Database.MigrateAsync(stoppingToken);
await foreach(var notification in _reader.ReadAllAsync(stoppingToken))
{
// process notification
}
}
uj5u.com熱心網友回復:
事實證明,該問題與另一個BackgroundService正在等待TimeSpan導致執行緒池饑餓的錯誤計算有關。
uj5u.com熱心網友回復:
雖然我認為對于提議的渠道解決方案可能存在爭議,就像之前提出的那樣,我會投票支持更簡單的解決方案,如果您愿意,渠道旨在處理大量訊息,所以如果有很多訊息,請考慮一下.
我懷疑你的 CPU 過高是因為你的通知佇列是空的,你沒有等待。
公共類工人:BackgroundService
{
私有只讀 ConcurrentQueue _messages = new ConcurrentQueue();
受保護的覆寫異步任務 ExecuteAsync(CancellationTokentoppingToken)
{
等待 Task.Factory.StartNew(() =>
{
while (!stoppingToken.IsCancellationRequested)
{
等待 _notificationsContext.Database.MigrateAsync(stoppingToken);
while (_messages.TryDequeue(out var notification) && !stoppingToken.IsCancellationRequested)
{
//處理通知
}
//當您沒有通知您不想進入我懷疑正在發生的瘋狂回圈時的情況下的顯式延遲
Task.Delay(1000,toppingToken).GetAwaiter().GetResult();
}
});
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/317075.html
上一篇:如何更改另一個類中的“框架內容”?(C#WPFXAML)
下一篇:C#屬性可以序列化為JSON嗎?
