如何让您的对象保持线程安全,实现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));
}
答案 0 :(得分:6)
所以......如果你不介意依赖于在编译时为你生成一些代码的扩展,那么它实际上是一种非常好的方式。无论如何,我正在使用Fody / PropertyChanged,这是一个非常容易的变化。这样就可以避免在真正没有业务知道UI的模型中引用SynchronizationContext
。
首先,安装NuGet提供的PropertyChanged.Fody。
当前实现INofityPropertyChanged
的每个类都应该具有属性[ImplementPropertyChanged]
。应删除手动实施。有关详细信息,请参阅readme和wiki。
需要实施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();
}
}
}
在某处设置PropertyChangedNotificationInterceptor.UIContext
。您可以将PropertyChangedNotificationInterceptor.UIContext = WindowsFormsSynchronizationContext.Current;
放在主窗体的构造函数中。
Form
constructor会经过Control constructor,如果尚不存在WindowsFormsSynchronizationContext
,最终会创建.Current
。因此,在此处捕获PropertyChanged
是安全的。 (注意:这可能是一个实现细节,可能会在将来的.NET版本,不同的平台等中发生变化。)
请记住,只有在您只有一个同步上下文(单个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
}
}