如何在没有SynchronizationContext的情况下使用INotifyPropertyChanged进行线程安全?

时间:2012-04-12 19:12:29

标签: c# multithreading inotifypropertychanged

如何让您的对象保持线程安全,实现INotifyPropertyChanged?我不能使用SynchronizationContext,因为我需要能够序列化对象。

    protected void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
           // What can I add here to make it thread-safe? 
            handler(this, new PropertyChangedEventArgs(propertyName));
    }

4 个答案:

答案 0 :(得分:6)

所以......如果你不介意依赖于在编译时为你生成一些代码的扩展,那么它实际上是一种非常好的方式。无论如何,我正在使用Fody / PropertyChanged,这是一个非常容易的变化。这样就可以避免在真正没有业务知道UI的模型中引用SynchronizationContext

  1. 首先,安装NuGet提供的PropertyChanged.Fody

  2. 当前实现INofityPropertyChanged的每个类都应该具有属性[ImplementPropertyChanged]。应删除手动实施。有关详细信息,请参阅readmewiki

  3. 需要实施PropertyChangedNotificationInterceptor。它们为wiki中的WPF提供了一个示例;我对WinForms项目的实现:

    public static class PropertyChangedNotificationInterceptor
    {
        public static SynchronizationContext UIContext { get; set; }
    
        public static void Intercept(object target, Action onPropertyChangedAction, string propertyName)
        {
            if (UIContext != null)
            {
                UIContext.Post(_ =>
                {
                    onPropertyChangedAction();
                }, null);
            }
            else
            {
                onPropertyChangedAction();
            }
        }
    }
    
  4. 在某处设置PropertyChangedNotificationInterceptor.UIContext。您可以将PropertyChangedNotificationInterceptor.UIContext = WindowsFormsSynchronizationContext.Current;放在主窗体的构造函数中。

    Form constructor会经过Control constructor,如果尚不存在WindowsFormsSynchronizationContext,最终会创建.Current。因此,在此处捕获PropertyChanged是安全的。 (注意:这可能是一个实现细节,可能会在将来的.NET版本,不同的平台等中发生变化。)

  5. 请记住,只有在您只有一个同步上下文(单个UI线程)时,这才有效。如果您遇到多个UI线程的混乱,需要多个数据绑定,这将变得更加复杂。所以请不要这样做。

    感谢Simon Cropp撰写{{1}}并帮助我找到它的特殊功能。

答案 1 :(得分:4)

如果您不幸并且必须使用Winforms,请尝试使用应用程序的MainForm来调用UI线程中的处理程序。不好的是你必须包括

using System.Windows.Forms;

protected void OnPropertyChanged(string propertyName)
{
    var handler = PropertyChanged;
    if (handler != null)
    {
        if (Application.OpenForms.Count == 0) return; 
        var mainForm = Application.OpenForms[0];
        if(mainForm == null) return; // No main form - no calls

        if (mainForm.InvokeRequired) 
        {
            // We are not in UI Thread now
            mainform.Invoke(handler, new object[] {
               this, new PropertyChangedEventArgs(propName)});
        }
        else
        {
            handler(this, new PropertyChangedEventArgs(propertyName)); 
        }              
    }
}

答案 2 :(得分:3)

如果您正在使用WPF,则可以使用Dispatcher封送对UI线程的调用。

App.Current.Dispatcher.Invoke(new Action(()=>{ 
    if (handler != null)
        handler(this, new PropertyChangedEventArgs(propertyName));
}));

答案 3 :(得分:1)

我知道这个问题已经得到解答了。但是我不久前遇到了同样的问题。 我的第一种方法与Dan Busha和其他人的答案非常相似:在gui同步上下文中调用PropertyChanged事件。 但在我看来,这不是最好的解决方案,因为你的模型需要知道GUI相关的东西。 并且绑定本身与GUI线程同步会更好。 在Windows窗体中有BindingSource。为了便于在Designer和其他绑定相关的东西中创建DataBindings。 我的想法是使用BindingSource来同步线程。基本上我的想法是覆盖所有OnBlaEvent并在GUI线程中调用它们。 只需像普通的绑定源一样使用它。 这就是我想出的:

using System.ComponentModel;
using System.Drawing;
using System.Threading;

namespace System.Windows.Forms.More
{
  [ToolboxBitmap(typeof(BindingSource))]
  public class SynchronizedBindingSource : BindingSource
  {

    #region constructors

    public SynchronizedBindingSource()
    {
      this.SynchronizationContext = SynchronizationContext.Current;
    }

    public SynchronizedBindingSource(IContainer container)
      : this()
    {
      container?.Add(this);
    }

    #endregion


    /// <summary>
    /// Changed events of binding source will be called from this synchronization context
    /// This is initialized at constructor
    /// </summary>
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public SynchronizationContext SynchronizationContext { get; set; }


    #region thread safe events

    protected override void OnAddingNew(AddingNewEventArgs e)
    {
      this.InvokeOnUiThread(() => base.OnAddingNew(e));
    }

    protected override void OnBindingComplete(BindingCompleteEventArgs e)
    {
      this.InvokeOnUiThread(() => base.OnBindingComplete(e));
    }

    protected override void OnCurrentChanged(EventArgs e)
    {
      this.InvokeOnUiThread(() => base.OnCurrentChanged(e));
    }

    protected override void OnCurrentItemChanged(EventArgs e)
    {
      this.InvokeOnUiThread(() => base.OnCurrentItemChanged(e));
    }

    protected override void OnDataError(BindingManagerDataErrorEventArgs e)
    {
      this.InvokeOnUiThread(() => base.OnDataError(e));
    }

    protected override void OnDataMemberChanged(EventArgs e)
    {
      this.InvokeOnUiThread(() => base.OnDataMemberChanged(e));
    }

    protected override void OnDataSourceChanged(EventArgs e)
    {
      this.InvokeOnUiThread(() => base.OnDataSourceChanged(e));
    }

    protected override void OnListChanged(ListChangedEventArgs e)
    {
      this.InvokeOnUiThread(() => base.OnListChanged(e));
    }

    protected override void OnPositionChanged(EventArgs e)
    {
      this.InvokeOnUiThread(() => base.OnPositionChanged(e));
    }

    private void InvokeOnUiThread(Action action)
    {
      this.SynchronizationContext?.Post(o => action(), null);
    }

    #endregion
  }
}