带线程的INotifyPropertyChanged

我有一个

BindingList 

它绑定到datagridview。 我class上的一个属性需要很长时间来计算,所以我对操作进行了操作。 在计算之后,我引发OnPropertyChanged()事件以通知网格值已准备好。

至少,这就是理论。 但是由于OnPropertyChanged方法是从差异线程调用的,所以我在网格的OnRowPrePaint方法中得到了一些常见的exception。

任何人都可以告诉我如何在主线程中推出OnPropertyChanged事件吗? 我不能使用Form.Invoke,因为类MyClass不知道它在Winforms应用程序中运行。

 public class MyClass : INotifyPropertyChanged { public int FastMember {get;set;} private int? slowMember; public SlowMember { get { if (slowMember.HasValue) return slowMember.Value; else { Thread t = new Thread(getSlowMember); t.Start(); return -1; } } } private void getSlowMember() { Thread.Sleep(1000); slowMember = 5; OnPropertyChanged("SlowMember"); } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propertyName) { PropertyChangingEventHandler eh = PropertyChanging; if (eh != null) { eh(this, e); } } } 

按照设计,控件只能由创建它的线程更新。这就是你得到exception的原因。

考虑使用BackgroundWorker ,只有在通过将事件处理程序订阅到RunWorkerCompleted后才能在持久操作完成后更新成员。

人们有时会忘记事件处理程序是MultiCastDelegate ,因此,它具有关于每个订阅者的所有信息,我们需要优雅地处理这种情况,而不会不必要地施加Invoke + Synchronization性能损失。 我一直在使用这样的代码:

 using System.ComponentModel; // ... public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { var handler = PropertyChanged; if (handler != null) { var e = new PropertyChangedEventArgs(propertyName); foreach (EventHandler h in handler.GetInvocationList()) { var synch = h.Target as ISynchronizeInvoke; if (synch != null && synch.InvokeRequired) synch.Invoke(h, new object[] { this, e }); else h(this, e); } } } 

它的作用很简单,但我记得我几乎已经破解了我的大脑然后试图找到最好的方法来做到这一点。

它首先“抓取”本地属性上的事件处理程序,以避免任何竞争条件。

如果处理程序不为null(至少存在一个订阅者),则准备事件args,然后遍历此多播委托的调用列表。

调用列表具有target属性,即事件的订阅者。 如果这个订阅者实现了ISynchronizeInvoke(所有UI控件都实现它),那么我们检查它的InvokeRequired属性,我们只是调用它来传递委托和参数。 以这种方式调用它会将调用同步到UI线程。

否则,我们只是直接调用事件处理程序委托。

这是我刚才写的东西; 它应该运作得相当好,但请注意大量更新的成本……

 using System.ComponentModel; using System.Threading; public class ThreadedBindingList : BindingList { SynchronizationContext ctx = SynchronizationContext.Current; protected override void OnAddingNew(AddingNewEventArgs e) { if (ctx == null) { BaseAddingNew(e); } else { ctx.Send(delegate { BaseAddingNew(e); }, null); } } protected override void OnListChanged(ListChangedEventArgs e) { if (ctx == null) { BaseListChanged(e); } else { ctx.Send(delegate { BaseListChanged(e); }, null); } } void BaseListChanged(ListChangedEventArgs e) { base.OnListChanged(e); } void BaseAddingNew(AddingNewEventArgs e) { base.OnAddingNew(e); } } 

考虑1:
在本文中查看UIThreadMarshal类及其用法:
UI模型层中的线程编组
您可以将类从static更改为instance并将其注入对象。 所以你的对象不会知道Form类。 它只会知道UIThreadMarshal类。

考虑2:
我不认为从你的财产返回-1是个好主意。 对我来说这看起来很糟糕。

考虑3:
也许你的class级不应该使用antoher线程。 也许是消费者类应该决定如何调用你的财产:直接或在一个单独的线程中。 在这种情况下,您可能需要提供其他属性,例如IsSlowMemberInitialized。