3

如何保持实现 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

4 回答 4

7

所以......事实证明,如果您不介意依赖扩展在编译时为您生成一些代码,那么实际上有一个非常好的方法。无论如何,我一直在使用 Fody/PropertyChanged,这使更改变得非常容易。这避免了必须引用SynchronizationContext真正没有业务了解 UI 的模型。

  1. 首先,安装可以从 NuGet 获得的PropertyChanged.Fody 。

  2. 当前实现的每个类都INofityPropertyChanged应该具有属性[ImplementPropertyChanged]。应该删除手动实现。有关更多信息,请参阅自述文件wiki

  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函数通过Control 构造函数,最终将创建WindowsFormsSynchronizationContextif 一个不存在。因此,在这里捕获是安全的.Current。(注意:这可能是一个实现细节,并且可能会在未来的 .NET 版本、不同平台等中发生变化。)

请记住,这仅在您只有一个同步上下文(单个 UI 线程)时才有效。如果您曾经陷入多个 UI 线程的混乱中,并且需要对多个 UI 线程进行数据绑定,那么这将变得更加复杂。所以请不要那样做。

感谢Simon Cropp的写作PropertyChanged并帮助我找到了它的这个特殊功能。

于 2014-12-17T02:40:16.203 回答
5

如果您使用的是 WPF,则可以使用 Dispatcher 编组对 UI 线程的调用。

App.Current.Dispatcher.Invoke(new Action(()=>{ 
    if (handler != null)
        handler(this, new PropertyChangedEventArgs(propertyName));
}));
于 2012-04-12T19:41:46.813 回答
5

如果您不走运并且必须使用 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)); 
        }              
    }
}
于 2013-11-26T16:11:15.367 回答
1

我知道这个问题已经回答了。但是我前段时间偶然发现了同样的问题。我的第一种方法与 Dan Busha 和其他人的答案非常相似:在 gui 同步上下文中调用 PropertyChanged 事件。但在我看来,这不是最好的解决方案,因为您的模型需要了解与 GUI 相关的内容。绑定本身与 GUI 线程同步会更好。在 Windows 窗体中有 BindingSource。为了便于在设计器中创建 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
  }
}
于 2018-03-12T12:36:03.003 回答