从 ConcurrentDictionary 中删除项目

Removing items from a ConcurrentDictionary

我有 Sky 对象队列,我的进程在其中拾取每个对象并执行它。如果需要结果,则将它们放入 ConcurrentDictionary 以使用 GUID 检索。天空对象每隔几毫秒就会从队列中添加和执行,这个过程将 运行 持续数小时。从 ConcurrentDictionary 检索结果会尝试删除并删除对象,但有些可能不会检索到,因为它们不需要。如果不需要,我已经实现了不创建结果的想法。作为以防万一的计划,我为每个天空对象添加了一个 UTC 创建日期。

我想创建一个清理进程,每隔几分钟 运行 查找任何早于 x 分钟的 UTC 时间并将其删除。据我了解 ConcurrentDictionary 我应该没有问题只是遍历集合并简单地删除它们,但在编写我的清理程序之前我有几个问题。

Should I run the cleanup process in a separate async task? Will my iterating or removing cause keep any results from being added to the ConcurrentDictionary, like blocking issues?

所以,我添加了第二个 ConcurrentDictionary。当我将结果添加到第一个字典时,我还将 guid 和 UTC 日期添加到第二个。下面的代码迭代第二个和它发现它从第一个中删除的任何过期项目。我还没有测试过这个,所以我不确定我是否可以在迭代字典时从秒中删除。

    /// <summary>
    /// Use to clean up an sky results objects that might be left in the results dictionary
    /// This compares the now UTC date to the object created date plus the seconds parameter
    /// </summary>
    /// <param name="seconds"></param>
    public static void CleanSkyResultDictionary(double seconds)
    {
        foreach (var skyresult in SkyCleanupDictionary)
        {
            if (skyresult.Value.AddSeconds(seconds) <= DateTime.UtcNow) continue;
            SkyResultDictionary.TryRemove(skyresult.Key, out _);
            SkyCleanupDictionary.TryRemove(skyresult.Key, out _);
        }
    }

可以从多个线程安全地访问 ConcurrentDictionary,而不会损坏内部字典数据结构。因此,您只需要一个字典实例,因为一个线程可以同时向其中添加内容,而另一个线程则可以对其进行迭代或从中删除。

1。数据

拥有两个词典意味着现在您应该真正同步它们,这将部分抵消使用并发词典的好处。

我建议将时间戳存储在同一个字典中。一种方法是:

class ToStore {
 //Constructor here, or add public sets

 public YourClass Data {get;}
 public DateTime AddedAtUtc {get;} 
 //I would suggest using NodaTime's Instant, but that's out of scope for this question.
}

public void Add(YourClass data )
{
    if (data == null)
    {
       throw new ArgumentNullException(nameof(data ));
    }

    var frame = new ToStore {
        Data = data,
        AddedUtc = DateTime.UtcNow 
    }

    dict.TryAdd(frame.TimestampUtc, frame);
    OnAdd(); // fire and forget
}

如果 key 可以是时间戳,你就不需要 ToStore class 这样会更简单。

2。清理

我不了解你的应用,但你可以考虑在添加新元素时而不是在计时器上进行清理。

    public void Add(YourOtherClass data )
    {
        (...)
        OnAdd(); // fire and forget
    }

    private void OnAdd()
    {
        Task.Run(() =>
        {
            CleanUp();
        }).ConfigureAwait(false);
    }

Cleanup 是:

        foreach (var kvp in dict.Where(IsStale))
        {
            // Please note that by now the frame may have been already
            // removed by another thread.
            dict.TryRemove(kvp.Key, out var ignored);
        }

其中 IsStale returns 如果框架足够旧可以删除,则为真。

希望对您有所帮助。