【问题标题】:Raising OnPropertyChanged when an object property is modified from another thread当从另一个线程修改对象属性时引发 OnPropertyChanged
【发布时间】:2016-03-09 19:59:56
【问题描述】:

所以我有这个对象:

public class SomeObject: INotifyPropertyChanged
{
         public decimal AlertLevel {
            get {
                return alertLevel;
            }
            set {
                if(alertLevel == value) return;
                alertLevel = value;
                OnPropertyChanged("AlertLevel");
            }

         private void OnPropertyChanged(string propertyName) {
            if(PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
}

假设我在不是 GUI 线程的线程上更改此对象。当我没有对此类中任何 GUI 组件的引用时,如何让该对象在与 GUI 相同的线程上引发 PropertyChanged 事件?

【问题讨论】:

  • 您必须使用调度程序。检查这个stackoverflow.com/questions/1644079/…
  • 是的,我看到了类似的,但是 Dispatcher 类在哪里?我需要哪个参考?我在任何地方都找不到它...

标签: c# .net multithreading winforms


【解决方案1】:

通常情况下,事件订阅者应负责在必要时将调用编组到 UI 线程。

但是,如果有问题的类是特定于 UI 的(也称为视图模型),一旦它在 UI 线程上创建,您就可以捕获 SynchronizationContext 并将其用于引发事件,例如这个:

public class SomeObject : INotifyPropertyChanged
{
    private SynchronizationContext syncContext;

    public SomeObject()
    {
        syncContext = SynchronizationContext.Current;
    }

    private decimal alertLevel;

    public decimal AlertLevel
    {
        get { return alertLevel; }
        set
        {
            if (alertLevel == value) return;
            alertLevel = value;
            OnPropertyChanged("AlertLevel");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            if (syncContext != null)
                syncContext.Post(_ => handler(this, new PropertyChangedEventArgs(propertyName)), null);
            else
                handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

您也可以通过构造函数传递SynchronizationContext

另一种方法是保持对象完好无损,但数据通过中间同步绑定源绑定到它,如此处所述Update elements in BindingSource via separate task

【讨论】:

  • 我的类的问题是它是绑定列表中的一个元素。所以你提供的文章很有趣。
  • 这在我第一次阅读时没有同步,但我现在看到了。发布了一个我最终选择的答案,现在意识到它与您的答案非常相似。
【解决方案2】:

对于 WPF - 添加以下引用:

PresentationFramework.dll
WindowsBase.dll

在您的后台线程中 - 将需要访问 UI 的代码包装到 dispatcher.Invoke() 中

using System.Windows;
using System.Windows.Threading;
...

//this is needed because Application.Current will be NULL for a WinForms application, since this is a WPF construct so you need this ugly hack
if (System.Windows.Application.Current  == null)
   new System.Windows.Application();

Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
  //Do Your magic here 
}), DispatcherPriority.Render);

供 WinForms 使用

  Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Render, new Action(() => {
       //Do Your magic here
   }));

【讨论】:

  • 你需要给PresentationFramework.dll添加一个引用,通常只用于WPF,但是dispatcher类对winforms和WPF都有效。
  • Intelisense 不显示它。尝试粘贴代码并编译。
  • 很奇怪..也许你是从单元测试中调用它的?应用程序启动了吗?
  • 是Winforms的WPF吗?
  • 这仍然不起作用...我明白为什么了:stackoverflow.com/questions/10448987/…
【解决方案3】:

一个更好的主意,不使用任何 WPF 引用:

public class GUIThreadDispatcher {
        private static volatile GUIThreadDispatcher itsSingleton;
        private WeakReference itsDispatcher;

        private GUIThreadDispatcher() { }

        public static GUIThreadDispatcher Instance
        {
            get
            {
                if (itsSingleton == null)
                    itsSingleton = new GUIThreadDispatcher();

                return itsSingleton;
            }
        }

        public void Init(Control ctrl) {
            itsDispatcher = new WeakReference(ctrl);
        }

        public void Invoke(Action method) {
            ExecuteAction((Control ctrl) => DoInGuiThread(ctrl, method, forceBeginInvoke: false));
        }

        public void BeginInvoke(Action method) {
            ExecuteAction((Control ctrl) => DoInGuiThread(ctrl, method, forceBeginInvoke: true));
        }

        private void ExecuteAction(Action<Control> action) {
            if (itsDispatcher.IsAlive) {
                var ctrl = itsDispatcher.Target as Control;
                if (ctrl != null) {
                    action(ctrl);
                }
            }
        }

        public static void DoInGuiThread(Control ctrl, Action action, bool forceBeginInvoke = false) {
            if (ctrl.InvokeRequired) {
                if (forceBeginInvoke)
                    ctrl.BeginInvoke(action);
                else
                    ctrl.Invoke(action);
            }
            else {
                action();
            }
        }
    }
}

并像这样初始化:

  private void MainForm_Load(object sender, EventArgs e) {

     //setup the ability to use the GUI Thread when needed via a static reference
     GUIThreadDispatcher.Instance.Init(this);  
     ...
  }

并像这样使用:

public class SomeObject: INotifyPropertyChanged
{
         public decimal AlertLevel {
            get {
                return alertLevel;
            }
            set {
                if(alertLevel == value) return;
                alertLevel = value;
                OnPropertyChanged("AlertLevel");
            }

          private void OnPropertyChanged(string propertyName) {
              GUIThreadDispatcher.Instance.BeginInvoke(() => {
              if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                });
        }}

【讨论】:

    【解决方案4】:

    根据https://lostechies.com/gabrielschenker/2009/01/23/synchronizing-calls-to-the-ui-in-a-multi-threaded-application/ 和上面 Ivan 的回答,找到了一个更好的答案无需对表单控件使用 Wea​​kReference 和 NO WPF 引用

       public class GUIThreadDispatcher {
        private static volatile GUIThreadDispatcher itsSingleton;
        private SynchronizationContext itsSyncContext;
    
        private GUIThreadDispatcher() {}
    
        /// <summary>
        /// This needs to be called on the GUI Thread somewhere
        /// </summary>
        public void Init() {
            itsSyncContext = AsyncOperationManager.SynchronizationContext;
        }
    
        public static GUIThreadDispatcher Instance
        {
            get
            {
                if (itsSingleton == null)
                    itsSingleton = new GUIThreadDispatcher();
    
                return itsSingleton;
            }
        }
    
        public void Invoke(Action method) {
            itsSyncContext.Send((state) => { method(); }, null);
        }
    
        public void BeginInvoke(Action method) {
            itsSyncContext.Post((state) => { method(); }, null);
        }
    }
    

    }

    然后像这样初始化:

      private void MainForm_Load(object sender, EventArgs e) {
    
         //setup the ability to use the GUI Thread when needed via a static reference
         GUIThreadDispatcher.Instance.Init();  
         ...
      }
    

    并像这样使用:

    public class SomeObject: INotifyPropertyChanged
    {
             public decimal AlertLevel {
                get {
                    return alertLevel;
                }
                set {
                    if(alertLevel == value) return;
                    alertLevel = value;
                    OnPropertyChanged("AlertLevel");
                }
    
              private void OnPropertyChanged(string propertyName) {
                      GUIThreadDispatcher.Instance.BeginInvoke(() => {
                         if (PropertyChanged != null)
                              PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                    });
            }}
    

    【讨论】:

      【解决方案5】:

      结果证明这是一个干净的实现(相对而言)。只需要包含对WindowsBase.dll 的引用,它原来是一个 WPF 库,所以嗯...,对它不是很满意,但它是一个解决方案...:

       public class GUIThreadDispatcher {
              private static volatile GUIThreadDispatcher itsSingleton;
              private Dispatcher itsDispatcher;
      
              private GUIThreadDispatcher() { }
              public static GUIThreadDispatcher Instance
              {
                  get
                  {
                      if (itsSingleton == null)
                          itsSingleton = new GUIThreadDispatcher();
      
                      return itsSingleton;
                  }
              }
      
              public void Init() {
                  itsDispatcher = Dispatcher.CurrentDispatcher;
              }
      
              public object Invoke(Action method, DispatcherPriority priority = DispatcherPriority.Render, params object[] args) {
                 return itsDispatcher.Invoke(method, priority, args);
              }
      
              public DispatcherOperation BeginInvoke(Action method, DispatcherPriority priority = DispatcherPriority.Render, params object[] args) {
                  return itsDispatcher.BeginInvoke(method, priority, args);
              }
      

      然后像这样初始化它:

      static class Program {
              /// <summary>
              /// The main entry point for the application.
              /// </summary>
              [STAThread]
              static void Main() {
                 GUIThreadDispatcher.Instance.Init();  //setup the ability to use the GUI Thread when needed via a static reference
                 Application.Run(new MainForm());
              }
          }
      

      然后像这样使用它:

      public class SomeObject: INotifyPropertyChanged
      {
               public decimal AlertLevel {
                  get {
                      return alertLevel;
                  }
                  set {
                      if(alertLevel == value) return;
                      alertLevel = value;
                      OnPropertyChanged("AlertLevel");
                  }
      
                private void OnPropertyChanged(string propertyName) {
                      GUIThreadDispatcher.Instance.BeginInvoke(() => {
                          if (PropertyChanged != null)
                               PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                      });
              }}
      

      【讨论】:

      • 如果您在 Application.Run( 之前调用 Dispatcher.CurrentDispatcher,我不能 100% 确定它的值是否正确。
      • 它似乎有效,但我明白你的意思。应该在 MainForm_Load 中初始化...
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-03-28
      • 2011-04-03
      • 2021-05-08
      • 2012-05-17
      • 2015-07-12
      • 1970-01-01
      • 2020-11-06
      相关资源
      最近更新 更多