C# 修改值时在ConcurrentDictionary中循环
我在C# 修改值时在ConcurrentDictionary中循环,c#,task-parallel-library,concurrentdictionary,C#,Task Parallel Library,Concurrentdictionary,我在static类中有一个静态ConcurrentDictionary。在类的静态构造函数中,我通过Task调用一个私有方法。运行无限期地循环字典并删除已过期的项,即,自添加这些项以来已经过了很长时间,因此需要删除它们(这是通过检查字典中每个项目的CustomItem值上的属性来确定的): 公共静态类ItemsHolder { 公共静态ConcurrentDictionary项=新建ConcurrentDictionary(); // ... 静态项目文件夹 { Task.Run(()=>Del
static
类中有一个静态ConcurrentDictionary
。在类的静态构造函数中,我通过Task调用一个私有方法。运行无限期地循环字典并删除已过期的项,即,自添加这些项以来已经过了很长时间,因此需要删除它们(这是通过检查字典中每个项目的CustomItem
值上的属性来确定的):
公共静态类ItemsHolder
{
公共静态ConcurrentDictionary项=新建ConcurrentDictionary();
// ...
静态项目文件夹
{
Task.Run(()=>DeleteExpired());
}
私有静态异步任务DeleteExpired()
{
while(true)
{
//检查字典是否为空,如果为空,
//等待任务。延迟一点等-为简洁起见省略
foreach(项目中的var项目)
{
var guid=item.Key;
var customItem=项目值;
if(customItem.ConditionToRemoveItemFromDictionaryHere)
{
var removietem=Items.TryRemove(guid,out);
// ...
}
}
}
}
}
在代码的其他部分(该类之外),项将被添加到此字典中,并根据其他条件删除。实际上,DeleteExpired()
用于清除无法从字典中删除的项(在代码的其他部分,出于XYZ原因)
我看到DeleteExpired()
由于占用CPU时间而不断出现在我的工具(如R#中的dotTrace)中。我知道,在其他线程可能正在添加/删除的情况下,通过并发字典进行迭代是无锁的,应该是安全的,但我不确定这是否是最有效的方法。是否有更有效/更高效的doi方法那么呢?如果字典不是空的,那么在每次迭代之间就不需要等待了
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
}
我会建议使用后台任务调度器,比如Quartz,因为调度器是专门为该用例设计的
创建清理字典的任务:
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 _);
// ...
}
}
}
}
}
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 _);
// ...
}
}
});
}
}
公共类CleanExpiredJob:IJob
{
//您的ConcurrentDictionary值将由调度程序注入
公共ConcurrentDictionary项{private get;set;}
公共任务执行(IJobExecutionContext上下文)
{
返回任务。运行(()=>{
foreach(项目中的var项目)
{
var guid=item.Key;
var customItem=项目值;
if(customItem.ConditionToRemoveItemFromDictionaryHere)
{
var removietem=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);
//从工厂获取调度程序实例
NameValueCollection道具=新的NameValueCollection
{
{“quartz.serializer.type”,“binary”}
};
变量工厂=新的StdSchedulerFactory(props);
var scheduler=wait factory.GetScheduler();
wait scheduler.Start();
var job=JobBuilder.Create().Build();
//把你的字典加在这里
//注意:保持与用于注入的作业中的属性相同的名称
job.JobDataMap[“项”]=项;
//将作业设置为每10秒运行一次
var trigger=TriggerBuilder.Create()
.StartNow()
.使用SimpleSchedule(x=>x
.间隔秒(1)
.RepeatForever())
.Build();
ScheduleJob(作业,触发器);
您可以使用SortedDictionary按到期日期订购商品,并从列表的开头提取。这不是并发的,但您可以使用N个这样的列表并使用锁切分,这应该会有很大帮助。您好@usr,您能解释一下为什么要使用N
这样的列表以及您所说的锁切分是什么意思吗?这是在执行时手动锁定集合吗ng对它的操作?@globetrotter-您不能从正在迭代的集合中删除项。它们是并发的*
集合还是标准集合并不重要。对于单个线程来说是这样。对于多个线程来说更糟。如果只有一个列表,所有线程都会在该锁上竞争。可能您正在使用ConcurrentDi因为很多线程都有很多操作。但实际上,你可以通过使用一个简单的字典并锁定它来提高效率。如果写入频率不太高,这将使迭代速度大大加快。这取决于你的写入频率。但是锁切分是解决写入争用的一种方法。顺便说一句,你可以使用锁切分和N个字典实例。这样,删除迭代会更快,也会更快。检查字典是否为空,如果为空,请等待任务。延迟一点,等等-为简洁起见省略。是否意味着仅当字典为空时才等待?如果是,则会影响性能。那里有一个代码注释,其中he说他没有等待。这是问题的关键,因为尽管等待得很好,他还是有一个性能问题。@usr OP中有一条评论,我说“是的,我只在字典为空时异步等待,所以如果没有,我不会尝试用foreach枚举”.我的母语不是英语,但我想这意味着如果收藏不是空的,他不会等。哦,我明白了。