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

C#中访问持续增长List报错的解决方案咨询

解决高频WebSocket数据写入与多线程读取的并发问题

首先,咱们得先搞懂你遇到的这个「Destination array was not long enough」异常到底是怎么来的:List<T>内部是用数组存储元素的,当你调用Add导致元素数量超过当前数组容量时,它会自动扩容——创建一个更大的新数组,把旧数组的元素复制过去。如果这个扩容过程刚好和另一个线程的ToList()操作撞在一起,读取线程拿到的是正在被替换的旧数组信息,就会抛出这个数组长度不匹配的错误。

你之前只在读取侧加了锁,但WebSocket的Add操作完全没加同步,这就导致写入和读取的并发冲突,锁等于白加了。接下来给你几个可行的解决方案,从简单修复到性能优化逐步来:

1. 给所有List操作加上同一把锁

最直接的修复方式是确保写入和读取操作都用同一个锁对象来同步,而不是锁liveQuotesList本身(虽然这里是static List不会被重新赋值,但用单独的锁对象是更规范的做法)。

修正WebSocket端的写入代码:

// 定义全局锁对象,要保证WebSocket和读取线程都能访问到
private static readonly object _listSyncLock = new object();
public static List<LiveMarketDataObject> liveQuotesList = new List<LiveMarketDataObject>();

// 接收消息后添加元素时:
lock (_listSyncLock)
{
    liveQuotesList.Add(_liveQuotes);
}

修正读取侧的代码:

// 复用同一个锁对象
private static readonly object _lock = socketWithEvaluation._listSyncLock;
private static List<LiveMarketDataObject> liveQuotesList = socketWithEvaluation.liveQuotesList;

// 查询时:
lock (_lock)
{
    var newQuoteList = liveQuotesList
        .Where(x => x.sym == symbolName && x.t >= startunixTime && x.t <= endunixTime)
        .ToList();
}

这样所有对liveQuotesList的操作都在锁的保护下,扩容和读取就不会冲突了。

2. 用ReaderWriterLockSlim提升并发性能

如果你的读取操作比写入更频繁,用普通的lock会让所有读线程互相阻塞,影响性能。这时候可以用ReaderWriterLockSlim——它允许多个读线程同时访问,只有写线程会独占锁,能大幅提升并发效率。

WebSocket端写入代码:

private static readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
public static List<LiveMarketDataObject> liveQuotesList = new List<LiveMarketDataObject>();

// 添加元素时:
_rwLock.EnterWriteLock();
try
{
    liveQuotesList.Add(_liveQuotes);
}
finally
{
    // 必须在finally里释放锁,避免异常导致锁泄漏
    _rwLock.ExitWriteLock();
}

读取侧代码:

private static ReaderWriterLockSlim _rwLock = socketWithEvaluation._rwLock;
private static List<LiveMarketDataObject> liveQuotesList = socketWithEvaluation.liveQuotesList;

// 查询时:
_rwLock.EnterReadLock();
try
{
    var newQuoteList = liveQuotesList
        .Where(x => x.sym == symbolName && x.t >= startunixTime && x.t <= endunixTime)
        .ToList();
}
finally
{
    _rwLock.ExitReadLock();
}

3. 优化数据结构,解决内存和查询效率问题

每分钟7万条数据,List会无限膨胀,不仅内存占用越来越高,每次查询遍历全量数据也会越来越慢。建议按时间分片存储,比如按小时划分数据块,同时自动清理旧数据:

WebSocket端分片写入代码:

// 用字典存储不同时间段的数据集,key是小时级别的时间戳
private static readonly Dictionary<long, List<LiveMarketDataObject>> _timePartitionedData = new Dictionary<long, List<LiveMarketDataObject>>();
private static readonly object _partitionLock = new object();

// 计算当前数据所属的小时分片(UTC时间,避免时区问题)
long currentHour = (DateTimeOffset.UtcNow.ToUnixTimeSeconds() / 3600) * 3600;

lock (_partitionLock)
{
    // 如果当前分片不存在,创建新的
    if (!_timePartitionedData.ContainsKey(currentHour))
    {
        _timePartitionedData[currentHour] = new List<LiveMarketDataObject>();
        
        // 自动清理24小时前的旧分片,防止内存溢出
        var expiredKeys = _timePartitionedData.Keys.Where(k => k < currentHour - 24 * 3600).ToList();
        foreach (var key in expiredKeys)
        {
            _timePartitionedData.Remove(key);
        }
    }
    _timePartitionedData[currentHour].Add(_liveQuotes);
}

读取侧分片查询代码:

var targetQuotes = new List<LiveMarketDataObject>();
// 计算查询时间范围覆盖的所有小时分片
long startHour = (startunixTime / 3600) * 3600;
long endHour = (endunixTime / 3600) * 3600;

lock (_partitionLock)
{
    for (long hour = startHour; hour <= endHour; hour += 3600)
    {
        if (_timePartitionedData.TryGetValue(hour, out var chunk))
        {
            // 只在当前分片里过滤数据,减少遍历量
            targetQuotes.AddRange(chunk.Where(x => 
                x.sym == symbolName && x.t >= startunixTime && x.t <= endunixTime));
        }
    }
}

这样既解决了并发问题,又优化了内存占用和查询效率,是高频数据场景下更合理的方案。

内容的提问来源于stack exchange,提问作者Ankit

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 05:03:27