在修改值的同时遍历ConcurrentDictionary

时间:2018-06-21 09:13:20

标签: c# task-parallel-library concurrentdictionary

我在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时间。我知道在其他线程可能要添加/删除的同时并发字典进行迭代是无锁的,并且应该是安全的,但是我不确定这是否是最有效的方法。有没有更有效的方法?

2 个答案:

答案 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);