2012-04-12 78 views
1

你如何让你的对象线程安全的实现INotifyPropertyChanged?我不能使用SynchronizationContext,因为我需要能够序列化对象。如何在没有SynchronizationContext的情况下使用INotifyPropertyChanged进行线程安全?

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

“我不能使用SynchronizationContext,因为我需要能够序列化对象。” - 这不应该是一个问题。你是否用'[NonSerialized]'标记了字段并添加了一个'OnDeserializing'方法来在反序列化时重置它? – hvd 2012-04-12 19:19:28

+0

我没有。有没有比在我创建对象时在SynchronizationContext中发送代码时更容易的方法?或者我只是太懒惰了:D – Stephen 2012-04-12 19:23:24

+0

如果您在希望事件处理程序运行的相同上下文中创建对象,则可以在该对象的构造函数中将该字段设置为“SynchronizationContext.Current”,否则,我不确定最好的办法是什么。 – hvd 2012-04-12 19:26:54

回答

6

所以......原来竟还有,如果你不介意依靠扩展在编译生成一些代码对你一个非常好的办法时间。无论如何,我使用Fody/PropertyChanged,这是一个非常简单的改变。这样可以避免在真正没有关于用户界面的业务知识的模型中参考SynchronizationContext

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

  2. 目前实施INofityPropertyChanged的每个班级应改为具有[ImplementPropertyChanged]的属性。应该删除手动执行。有关更多信息,请参阅readmewiki

  3. A 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是安全的。 (注:这可能是一个实现细节,并可能在未来的.NET版本,不同平台等改变)

请记住,这仅适用于如果你只有一个单一的同步上下文(单UI线程)。如果你遇到过多个UI线程的混乱,需要多个数据绑定,这将变得更加复杂。所以请不要这样做。

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

3

如果您使用的是WPF,则可以使用分派器将您的调用编组到UI线程。

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

不幸的是我必须使用Forms。 – Stephen 2012-04-12 19:55:57

+0

@Stephen你可以调用WinForms中的UI线程,通过调用控件上的调用,但这很笨重,不是很优雅 – 2012-04-12 20:11:23

+1

一小时的搜索...终于 - 一个优秀,干净,简洁和工作的答案(无论如何wpf) 。谢谢! – Rob 2014-09-16 03:45:49

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)); 
     }    
    } 
} 
相关问题