使用Parallel.ForEach时如何更新共享状态?WPF处理PST文件相关疑问
WPF PST邮件处理并行改造线程安全问题解答
前置注意事项
现有代码是异步实现,直接套用到Parallel.ForEach会出现无法等待所有任务完成的问题:Parallel.ForEach不支持异步委托,传入async方法会被识别为返回void的委托,执行时会变成"火并忘"逻辑,你没法等到所有邮件处理完成再执行后续操作。如果你的运行环境是.NET 6及以上,更推荐用专门适配异步场景的Parallel.ForEachAsync,如果是低版本可以用SemaphoreSlim + Task.WhenAll实现限流异步并行,更适合你这种IO密集型的PDF生成场景。
问题逐一解答
- 循环中传递的
logFile变量是否会引发问题?StreamWriter本身没有内置线程安全保护,多线程并行调用WriteLine会出现日志内容乱序、多段日志拼接错乱,甚至直接丢失日志条目。临时解决方案是给Log方法加锁:
static void Log(StreamWriter logFile, string msg) { lock(logFile) { logFile.WriteLine(DateTime.Now.ToString("yyMMdd-HHmmss.fff") + " - " + msg); } }
后续替换为专业日志框架后不需要自己处理锁,框架内部已经实现了线程安全逻辑。
- 循环内更新
nCurr是否安全?有没有更优的实现方式?
普通的nCurr++不是原子操作,多线程同时自增会出现计数丢失,最终结果比实际处理的邮件数小。最优实现是用系统提供的原子操作类:
int nCurr = 0; // 循环内自增代码 int current = Interlocked.Increment(ref nCurr); // 用current变量更新进度即可
Interlocked.Increment是硬件级别的原子操作,没有锁的开销,效率最高。
主循环内仅对
_allFiles执行添加操作,不读取也不删除,该操作是否安全?
不安全。List<T>的Add方法没有做线程安全处理,多线程同时调用会出现元素丢失、内部索引错乱,极端情况下还会抛出运行时异常。ProcessMessage方法内也会更新_allFiles,这个问题的答案是否和上一个问题一致?
完全一致。不管在哪个层级调用Add,只要是多线程同时操作同一个List实例,就会有线程安全问题。解决方案两种:
- 替换为线程安全集合
ConcurrentBag<string>,天生支持多线程并发添加 - 保留
List,每次添加时加锁:lock(_allFiles) { _allFiles.Add(fileName); }
- 循环内更新BusyContent、调用
Application.Current.Dispatcher.Invoke是否存在问题?Dispatcher.Invoke本身是线程安全的,它会把操作封送到UI线程执行,所以更新ObservableCollection的操作不会报跨线程异常。但有两个需要注意的点:
- 如果并行度很高,频繁调用
Dispatcher.Invoke会挤占UI线程资源,导致界面卡顿,可以考虑批量更新或者降低进度刷新频率 - 如果
BusyContent是绑定到UI的属性(实现了INotifyPropertyChanged),直接在工作线程更新会触发跨线程更新绑定源异常,建议把BusyContent的更新也放到Dispatcher.Invoke里执行。
内容的提问来源于stack exchange,提问作者Avrohom Yisroel
相关产品推荐
相关产品推荐

