带线程的INotifyPropertyChanged

Jür*_*ock 6 .net c# datagridview thread-safety winforms

我有一个

 BindingList<T>
Run Code Online (Sandbox Code Playgroud)

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

至少,这就是理论.但是,由于从一个差异线程调用了OnPropertyChanged方法,我在网格的OnRowPrePaint方法中得到了一些已经存在的异常.

任何人都可以告诉我如何在主线程中推出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);
        }
   }

}
Run Code Online (Sandbox Code Playgroud)

Lou*_*ier 9

人们有时会忘记事件处理程序是一个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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

  • 我不得不将`EventHandler`重命名为`PropertyChangedEventHandler`,因为我得到一个`System.InvalidCastException`,其中详细信息`{"无法将'System.ComponentModel.PropertyChangedEventHandler'类型的对象强制转换为'System.EventHandler'."} `我有一个在内部订阅事件的UI线程中创建的BindingList,但是同步变量总是返回null,因为h.Target为null. (2认同)

Yan*_*ton 8

按照设计,控件只能通过创建它的线程进行更新.这就是您获得异常的原因.

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