从另一个线程

时间:2016-03-09 19:59:57

标签: c# .net multithreading winforms

所以我有这个对象:

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事件?

5 个答案:

答案 0 :(得分:5)

通常情况下,事件订阅者应负责在必要时编组对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所述。

答案 1 :(得分:2)

for 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
   }));

答案 2 :(得分:1)

更好的想法,不使用任何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));
                });
        }}

答案 3 :(得分:0)

这结果是一个干净的实现(相对)。只需要包含一个对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));
                });
        }}

答案 4 :(得分:0)

找到一个更好的答案,而不必使用WeakReference到表单控件和NO WPF引用基于https://lostechies.com/gabrielschenker/2009/01/23/synchronizing-calls-to-the-ui-in-a-multi-threaded-application/和Ivan的答案:

   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));
                });
        }}