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

C# .NET异步方法内向列表异步添加元素的线程安全性问题

问题解答

核心结论

  • .NET 不会 自动为List<T>的并发写入操作加锁,List<T>本身属于非线程安全类型,基础类库中的普通集合类型默认都没有内置锁逻辑,避免不必要的性能开销,因此不会自动处理并发写入的线程安全问题。多个线程同时调用Add方法会出现元素丢失、索引异常甚至程序崩溃的问题,即使你不关心错误的存储顺序,也不能直接无保护并发写入。

可选解决方案

方案1:自行加锁控制并发写入

如果你需要实时把异常写入错误列表,可以通过加锁的方式解决线程安全问题,改动量很小:
首先给ExampleObject新增一个锁对象:

public class ExampleObject
{
    public List<Exception> errors = new List<Exception>();
    // 新增只读锁对象,避免被意外修改
    public readonly object SyncRoot = new object();
}

然后修改异常写入逻辑:

catch (Exception e)
{
    lock (objectA.SyncRoot)
    {
        objectA.errors.Add(e);
    }
}

方案2:统一收集异常后批量写入(更推荐)

不需要处理锁逻辑,代码可维护性更高,完全规避并发写入风险:
把GetByIdAsync调整为返回捕获到的异常,所有任务执行完成后统一过滤、添加到错误列表:

async Task Main()
{
    var objectA = new ExampleObject();
    List<Task<Exception>> getIdTasks = new List<Task<Exception>>();
    foreach (var id in ids)
    {
        getIdTasks.Add(GetByIdAsync(id));
    }
    // 等待所有任务执行完成,获取所有返回结果
    var allResults = await Task.WhenAll(getIdTasks);
    // 过滤掉成功任务返回的null,批量加入错误列表
    objectA.errors.AddRange(allResults.Where(e => e != null));
}

private async Task<Exception> GetByIdAsync(object id)
{
    try
    {
        var objectFromId = await DoSomethingAsync(id);
        // 此处处理正常业务逻辑
        return null;
    }
    catch (Exception e)
    {
        return e;
    }
}

原代码注意事项

  • 存在变量名拼写错误:定义的任务列表变量为getIdtasks,添加任务时误用了tasks
  • Task.WhenAll拼写错误,注意大小写,不要写成Tasks.Whenall
  • GetByIdAsync包含await操作,需要添加async关键字标记为异步方法

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 20:06:00