Blazor WASM中Parallel与async配合时List.Add结果异常排查
问题根源
- 核心原因是
List<T>不是线程安全类型:你的代码通过Parallel.ForEachAsync并发执行Test4方法,多个线程会同时调用emptyTestlist.Add(i)修改集合,并发写入会破坏List内部的下标计数、扩容逻辑,导致部分数据被覆盖丢失,所以Count经常小于4。 - 添加
Console.WriteLine后恢复正常是巧合:Console.WriteLine内部自带全局同步锁,执行时会强制线程排队,同时引入了微小的时间差,刚好错开了并发写入的冲突窗口,但这不是可靠的修复方案,只要CPU负载、运行环境变化,问题依旧会复现。
另外你现有代码还有一个隐藏问题:Parallel.Invoke接收异步lambda时会生成async void方法,执行过程中如果抛出未捕获异常会直接导致进程崩溃,不建议这么用。
解决方案
你可以根据自己的场景任选以下一种方案:
方案1:给集合写入操作加同步锁
定义全局锁对象,在修改emptyTestlist时加锁,确保同一时间只有一个线程修改集合:// 定义锁对象 private readonly object _listLock = new object(); async Task Test4(int i) { await Task.Delay(300); // 加锁保证写入安全 lock(_listLock) { emptyTestlist.Add(i); } Console.WriteLine($"from TEST4: {i}"); }方案2:使用线程安全集合
直接将emptyTestlist的类型替换为System.Collections.Concurrent命名空间下的线程安全集合,例如ConcurrentBag<int>,不需要手动加锁就可以支持并发写入:// 替换原来的List<int> emptyTestlist定义 ConcurrentBag<int> emptyTestlist = new ConcurrentBag<int>();方案3(最推荐):避免共享集合写入,直接收集并发任务返回值
你的场景完全不需要共享可变集合,只需要让每个Test4返回处理结果,等待所有任务完成后统一收集结果即可,从根源上消除并发冲突:async ValueTask Test2() { cts = new CancellationTokenSource(); // 并发执行所有Test4,统一收集返回结果 var results = await Task.WhenAll(testList.Select(test => Test4(test))); emptyTestlist = results.ToList(); foreach (var test in emptyTestlist) { await Test3(test); } } // 修改Test4返回处理后的结果 async Task<int> Test4(int i) { await Task.Delay(300); Console.WriteLine($"from TEST4: {i}"); return i; }
另外针对你的Blazor WASM场景,建议把定时循环逻辑改成托管服务或者在组件生命周期中用CancellationToken控制启停,避免组件销毁后后台任务继续运行导致内存泄漏。
内容的提问来源于stack exchange,提问作者otter otter
相关产品推荐
相关产品推荐

