如何传递属性至方法实现进度条更新?并发异常排查
第一个问题:如何传递属性到方法避免重复代码
你遇到的核心问题是属性不能作为ref参数传递——因为属性本质是get/set方法,而非直接的内存字段,所以C#不允许把属性当作ref/out参数传入。不过我们可以用委托来封装属性的读写逻辑,完美替代重复代码的问题。
方案1:封装递增逻辑(最简洁)
如果你的需求只是在循环里递增属性,直接传递一个Action委托来执行递增操作即可:
// 通用方法,不需要关心具体是哪个属性 private static void ProcessFiles(IEnumerable<string> fileList, List<Tuple<string, string>> errorList, Action incrementProgress) { foreach (var file in fileList) { if (!File.Exists(file)) { errorList.Add(Tuple.Create(file, " does not exist in the folder")); } // 执行外部传入的递增逻辑 incrementProgress(); } }
调用的时候直接传入对应属性的递增操作:
// 绑定ProgressBarProp1 ProcessFiles(fileList, errorList, () => _ViewModel.ProgressBarProp1++); // 绑定ProgressBarProp2 ProcessFiles(fileList, errorList, () => _ViewModel.ProgressBarProp2++);
方案2:封装读写逻辑(更灵活)
如果之后需要对属性做更复杂的操作(比如设置任意值),可以传递Func<int>(获取当前值)和Action<int>(设置新值)两个委托:
private static void ProcessFiles(IEnumerable<string> fileList, List<Tuple<string, string>> errorList, Func<int> getProgress, Action<int> setProgress) { foreach (var file in fileList) { if (!File.Exists(file)) { errorList.Add(Tuple.Create(file, " does not exist in the folder")); } // 获取当前值+1,再设置回去 setProgress(getProgress() + 1); } }
调用示例:
ProcessFiles(fileList, errorList, () => _ViewModel.ProgressBarProp1, p => _ViewModel.ProgressBarProp1 = p);
第二个问题:并发更新进度条的异常行为
你看到的现象是线程竞争导致的更新丢失,原因很简单:int的++操作并不是原子的,它拆分成了三个步骤:
- 读取当前属性值
- 数值加1
- 将新值写回属性
当两个线程同时执行这三步时,会出现“线程A读取值为5,线程B也读取值为5,两者都加1变成6,最后都写回6”的情况——相当于两次递增只生效了一次,看起来就像只有一个线程在更新。
另外还要注意:List<T>不是线程安全的,多个线程同时往errorList里加元素,可能会抛出异常或者丢失数据!
解决办法
1. 用原子操作实现线程安全的递增
使用Interlocked.Increment方法,它会在硬件层面保证递增操作的原子性,避免竞争问题:
如果你的ViewModel有私有字段存储进度值(假设是_progressBarProp1),可以直接用:
var taskList = new List<Task> { Task.Run(() => { Method1(fileList, errorList, () => Interlocked.Increment(ref _ViewModel._progressBarProp1)); }), Task.Run(() => { Method2(fileList, errorList, () => Interlocked.Increment(ref _ViewModel._progressBarProp1)); }) }; await Task.WhenAll(taskList);
更优雅的方式是在ViewModel里封装线程安全的递增方法(同时处理UI线程更新,因为WPF绑定属性不能在后台线程直接修改):
// ViewModel内部代码 private int _progressBarProp1; public int ProgressBarProp1 { get => _progressBarProp1; private set { _progressBarProp1 = value; OnPropertyChanged(nameof(ProgressBarProp1)); // 假设实现了INotifyPropertyChanged } } public void IncrementProgress1() { // 原子递增 var newValue = Interlocked.Increment(ref _progressBarProp1); // 切换到UI线程更新绑定属性 Application.Current.Dispatcher.Invoke(() => ProgressBarProp1 = newValue); }
调用时直接传入这个方法:
var taskList = new List<Task> { Task.Run(() => Method1(fileList, errorList, _ViewModel.IncrementProgress1)), Task.Run(() => Method2(fileList, errorList, _ViewModel.IncrementProgress1)) }; await Task.WhenAll(taskList);
2. 保证errorList的线程安全
把List<Tuple<string, string>>换成线程安全的ConcurrentBag<T>,或者在添加元素时加锁:
// 用ConcurrentBag替代List var errorList = new ConcurrentBag<Tuple<string, string>>(); // 或者加锁(如果坚持用List) lock (errorList) { errorList.Add(Tuple.Create(file, " does not exist in the folder")); }
内容的提问来源于stack exchange,提问作者windowsgm

