并发任务下StringBuilder无法按正确顺序追加内容的问题
解决Task并发下StringBuilder的顺序与线程安全问题
嗨,刚好踩过类似的坑,来给你捋清楚问题根源和解决方案~
首先你遇到的两个核心问题:
- StringBuilder不是线程安全的:多个Task同时调用
Append时,会出现竞态条件,轻则内容错乱,重则抛出异常; - 并发任务的执行顺序不可控:就算每个任务都能正确获取结果,它们的
Append操作顺序也和原单线程循环不一样,导致最终的表达式顺序不符合预期。
下面给你两种针对性的解决方案,根据你的需求选就行:
方案1:保持原顺序 + 并发执行GetResult(推荐)
这个方案既利用并发提升GetResult的执行速度,又能严格保证最终内容的顺序,同时避免StringBuilder的线程安全问题——因为我们只在单线程里操作它。
代码示例:
StringBuilder expression = new StringBuilder(1024); int count = table.Rows.Count; // 先创建所有任务,收集每个任务的结果 var tasks = new Task<string>[count]; for (int i = 0; i < count; i++) { // 一定要捕获当前循环的变量副本,避免闭包陷阱! int currentIndex = i; tasks[currentIndex] = Task.Run(() => GetResult(companyID)); } // 等待所有任务完成,得到按原顺序排列的结果数组 string[] results = await Task.WhenAll(tasks); // 按原顺序逐个追加到StringBuilder foreach (var result in results) { expression.Append(result); }
关键提示:
这里的currentIndex变量很重要——如果直接在Task里用循环变量i,所有任务会捕获同一个变量的引用,最后都会用循环结束时的count值,导致逻辑错误。
方案2:不需要严格顺序,只保证线程安全
如果你的场景不关心最终内容的顺序,只需要所有结果都被正确追加,可以给StringBuilder的操作加锁,或者用线程安全的集合先收集结果:
加锁方式:
StringBuilder expression = new StringBuilder(1024); // 定义一个锁对象,保证同一时间只有一个线程操作StringBuilder object appendLock = new object(); int count = table.Rows.Count; var tasks = new List<Task>(); for (int i = 0; i < count; i++) { int currentIndex = i; tasks.Add(Task.Run(() => { string result = GetResult(companyID); // 加锁后再执行Append lock (appendLock) { expression.Append(result); } })); } await Task.WhenAll(tasks);
线程安全集合收集方式:
var resultBag = new ConcurrentBag<string>(); int count = table.Rows.Count; var tasks = new List<Task>(); for (int i = 0; i < count; i++) { tasks.Add(Task.Run(() => { string result = GetResult(companyID); resultBag.Add(result); })); } await Task.WhenAll(tasks); // 最后将所有结果拼接(顺序不确定) StringBuilder expression = new StringBuilder(1024); foreach (var result in resultBag) { expression.Append(result); }
为什么原单线程代码没问题?
原代码是单线程循环,每次GetResult和Append都是按i的顺序依次执行,不存在多个线程同时操作StringBuilder的情况,自然不会有顺序和线程安全问题。
内容的提问来源于stack exchange,提问作者want_to_be_calm
相关产品推荐
相关产品推荐

