C#中使用INotifyPropertyChanged时如何避免无限循环?
解决INotifyPropertyChanged引发的自触发循环问题
这确实是使用INotifyPropertyChanged时很容易踩的坑——当变更发起者本身也是监听器时,很容易陷入无限循环,而且存属性副本的方式不仅繁琐,还增加了维护成本。分享几个不用放弃属性、也不用依赖StackTrace的靠谱方案:
方案一:自定义带发起者信息的PropertyChanged事件参数
核心思路是在通知里带上变更的发起者,让监听器可以直接判断是否要忽略这个通知。
首先,自定义一个继承自PropertyChangedEventArgs的类,增加发起者属性:
public class PropertyChangedWithSenderEventArgs : PropertyChangedEventArgs { public object Sender { get; } public PropertyChangedWithSenderEventArgs(string propertyName, object sender) : base(propertyName) { Sender = sender; } }
然后修改你的类,在触发事件时使用这个新的参数(兼容原有INotifyPropertyChanged接口):
public class YourMultiInstanceClass : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string _propertyA; public string PropertyA { get => _propertyA; set { if (_propertyA != value) { _propertyA = value; // 触发事件时传入当前变更的发起者 OnPropertyChanged(new PropertyChangedWithSenderEventArgs(nameof(PropertyA), this)); } } } protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { PropertyChanged?.Invoke(this, e); } }
最后,在你的网络管理器(监听器)里,判断通知的发起者是不是自己,如果是就跳过处理:
public class NetworkManager { public void SubscribeToPropertyChanges(YourMultiInstanceClass target) { target.PropertyChanged += OnPropertyChanged; } private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) { // 检查是否是自定义参数,并且发起者是当前管理器实例 if (e is PropertyChangedWithSenderEventArgs args && args.Sender == this) { return; // 忽略自己发起的变更通知 } // 处理其他发起者的变更逻辑 if (e.PropertyName == nameof(YourMultiInstanceClass.PropertyA)) { // 这里修改属性时,会触发通知,但因为发起者是this,会被上面的判断跳过 ((YourMultiInstanceClass)sender).PropertyA = FetchNewValueFromNetwork(); } } }
方案二:线程安全的临时忽略标记
如果不想修改事件参数,可以在你的类里加一个线程本地的忽略标记,确保多线程场景下不会互相干扰:
public class YourMultiInstanceClass : INotifyPropertyChanged { // 使用ThreadLocal确保每个线程有独立的标记,避免多线程冲突 private readonly ThreadLocal<bool> _isSettingProperty = new ThreadLocal<bool>(); public event PropertyChangedEventHandler PropertyChanged; private string _propertyA; public string PropertyA { get => _propertyA; set { if (_propertyA != value) { _isSettingProperty.Value = true; // 设置标记,通知时忽略 _propertyA = value; OnPropertyChanged(nameof(PropertyA)); _isSettingProperty.Value = false; // 重置标记 } } } protected virtual void OnPropertyChanged(string propertyName) { // 如果当前正处于设置属性的状态,就不触发通知 if (!_isSettingProperty.Value) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
如果希望通知依然触发,但让监听器能判断是否是“内部设置”,可以把这个标记通过自定义事件参数传递,监听器收到通知时检查即可。
为什么不推荐StackTrace?
你担心的点完全正确:StackTrace在多线程场景下不可靠——线程的调用栈可能被切换,而且解析调用栈的性能开销很大,频繁调用会影响程序运行效率,绝对不是生产环境的最优解。
上面两个方案都保留了属性的使用,不需要维护属性副本,而且在多线程场景下也能稳定工作,你可以根据自己的代码结构选择适合的方案。
内容的提问来源于stack exchange,提问作者AmateurD
相关产品推荐
相关产品推荐

