You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现列表元素到期自动移除?求线程安全高效实现方案

优雅实现带自动过期移除的元素列表

问题回顾

你需要一个能自动在指定时间移除元素并执行回调的列表,原实现用Task.Delay().ContinueWith()存在线程安全隐患,还没法处理元素提前移除、批量清空任务的需求,而且担心大量任务创建的合理性。

我的解决方案

这里我们用元素包装类+线程安全集合+可取消任务的组合,既能解决线程安全问题,又能灵活处理各种边界场景,代码简洁易维护:

1. 定义带取消能力的元素包装类

每个元素需要绑定一个取消令牌源,用来在元素被手动移除或列表清空时终止对应的过期任务:

private class ExpiringItem
{
    public object Value { get; }
    public DateTime ExpiryTime { get; }
    public Callback OnExpiry { get; }
    public CancellationTokenSource CancelSource { get; }

    public ExpiringItem(object value, DateTime expiryTime, Callback onExpiry)
    {
        Value = value;
        ExpiryTime = expiryTime;
        OnExpiry = onExpiry;
        CancelSource = new CancellationTokenSource();
    }
}

2. 替换成线程安全的集合

把原来的List<Tuple<...>>换成ConcurrentDictionary(用元素本身当键,确保线程安全的增删查):

private readonly ConcurrentDictionary<object, ExpiringItem> _expiringItems = new ConcurrentDictionary<object, ExpiringItem>();

如果你的元素可能重复(同一个对象被多次添加),可以改用Guid作为唯一键,避免冲突。

3. 实现安全的Add方法

先把元素存入集合,再启动可取消的异步任务,任务触发时先尝试移除元素(确保没被手动移除过)再执行回调:

public void Add(object item, DateTime expiry, Callback callback)
{
    if (item == null) throw new ArgumentNullException(nameof(item));
    // 处理已过期的情况,直接执行回调
    if (expiry <= DateTime.Now)
    {
        callback.Notify(item);
        return;
    }

    var delay = expiry - DateTime.Now;
    var expiringItem = new ExpiringItem(item, expiry, callback);

    // 先添加到集合再启动任务,避免任务执行时元素还没存入
    if (_expiringItems.TryAdd(item, expiringItem))
    {
        _ = Task.Run(async () =>
        {
            try
            {
                await Task.Delay(delay, expiringItem.CancelSource.Token);
                // 任务没被取消,尝试移除元素并执行回调
                if (_expiringItems.TryRemove(item, out var removedItem))
                {
                    removedItem.OnExpiry.Notify(removedItem.Value);
                }
            }
            catch (OperationCanceledException)
            {
                // 任务被取消,说明元素已经被手动移除或列表清空,无需处理
            }
            finally
            {
                // 释放取消令牌资源
                expiringItem.CancelSource.Dispose();
            }
        });
    }
}

关于任务数量的问题:.NET的Task.Delay底层是用线程池定时器实现的,不会为每个延迟创建新线程,所以大量任务也不会带来太大性能开销,完全不用担心。

4. 手动移除元素的方法

当需要提前移除元素时,取出对应的取消令牌源并取消任务:

public bool TryRemove(object item)
{
    if (_expiringItems.TryRemove(item, out var expiringItem))
    {
        expiringItem.CancelSource.Cancel();
        return true;
    }
    return false;
}

5. 清空列表并终止所有任务

遍历所有元素取消任务,然后清空集合:

public void Clear()
{
    foreach (var expiringItem in _expiringItems.Values)
    {
        expiringItem.CancelSource.Cancel();
        expiringItem.CancelSource.Dispose();
    }
    _expiringItems.Clear();
}

额外优化建议

  • 给你的类实现IDisposable接口,在Dispose方法里调用Clear(),确保程序退出时所有资源被正确释放。
  • 如果元素的GetHashCode/Equals不可靠,给每个元素分配唯一Guid作为ConcurrentDictionary的键即可。

内容的提问来源于stack exchange,提问作者Mr. Boy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 17:32:37