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.WhenallGetByIdAsync包含await操作,需要添加async关键字标记为异步方法
内容的提问来源于stack exchange,提问作者Jay
相关产品推荐
相关产品推荐

