我在ConcurrentDictionary
类中有一个静态static
。在该类的静态构造函数中,我通过Task.Run
调用一个私有方法来无限循环浏览字典并删除已过期的项,即自添加它们以来已经过去了很长时间,因此需要删除(这是通过检查字典中每个项目的每个CustomItem
值的属性来确定的):
public static class ItemsHolder
{
public static ConcurrentDictionary<Guid, CustomItem> Items = new ConcurrentDictionary<Guid, CustomItem>();
// ...
static ItemsHolder
{
Task.Run(() => DeleteExpired());
}
private static async Task DeleteExpired()
{
while (true)
{
// check if dictionary is empty and if yes,
// await Task.Delay a bit, etc - omitted for brevity
foreach (var item in Items)
{
var guid = item.Key;
var customItem = item.Value;
if (customItem.ConditionToRemoveItemFromDictionaryHere)
{
var removeItem = Items.TryRemove(guid, out _);
// ...
}
}
}
}
}
在代码的其他部分(此类之外),项目将添加到此字典中,并根据其他条件删除。实际上,DeleteExpired()
可以清除无法从字典中删除的项目(由于XYZ原因,在代码的其他部分)
我看到DeleteExpired()
不断在我的指标(如R#中的dotTrace)中弹出,占用了CPU时间。我知道在其他线程可能要添加/删除的同时并发字典进行迭代是无锁的,并且应该是安全的,但是我不确定这是否是最有效的方法。有没有更有效的方法?
答案 0 :(得分:1)
如果字典不为空,则不必在每次迭代之间等待。
while (true)
使线程疯狂旋转并且没有休息(从字面上看)。您必须在每次检查之间绝对等待。
在没有延迟或睡眠的情况下,一个线程将持续消耗CPU的整个核心(或者至少将尝试消耗它),这就是性能异常的原因。
我将其写为:
while(true) {
Items.Where(pair => pair.Value.ConditionToRemoveItemFromDictionaryHere)
.Select(pair => pair.Key)
.ToList()
.ForEach(guid => Items.TryRemove(guid, out _));
// thread.sleep, task delay or whatever suits your needs
}
答案 1 :(得分:0)
我建议使用诸如 Quartz 之类的后台任务计划程序,因为计划程序专门针对该用例而设计。
创建用于清理字典的任务:
public class CleanExpiredJob : IJob
{
// Your ConcurrentDictionary value will be injected by the scheduler
public ConcurrentDictionary<TKey, TValue> Items {private get; set;}
public Task Execute(IJobExecutionContext context)
{
return Task.Run(() => {
foreach (var item in Items)
{
var guid = item.Key;
var customItem = item.Value;
if (customItem.ConditionToRemoveItemFromDictionaryHere)
{
var removeItem = Items.TryRemove(guid, out _);
// ...
}
}
});
}
}
然后,将调度程序添加到您的应用程序开始中:
// Grab the Scheduler instance from the Factory
NameValueCollection props = new NameValueCollection
{
{ "quartz.serializer.type", "binary" }
};
var factory = new StdSchedulerFactory(props);
var scheduler = await factory.GetScheduler();
await scheduler.Start();
var job = JobBuilder.Create<CleanExpiredJob>().Build();
// Add your dictionary here
// Notice: Keep the same name as the property in the job for injection
job.JobDataMap["Items"] = Items;
// Set the job to run every 10 seconds
var trigger = TriggerBuilder.Create()
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(1)
.RepeatForever())
.Build();
scheduler.ScheduleJob(job, trigger);