How do you keep your objects thread-safe that implement INotifyPropertyChanged? I can't use a SynchronizationContext because I need to be able to serialize the object.
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));
}
So... turns out there's actually a very nice way if you don't mind relying on an extension generating some code for you at compile time. I was using Fody/PropertyChanged anyway, making this a very easy change. This avoids having to a reference to a SynchronizationContext
in models that really have no business knowing about the UI.
First, install PropertyChanged.Fody, available from NuGet.
Every class that currently implements INofityPropertyChanged
should instead have the attribute [ImplementPropertyChanged]
. The manual implementation should be removed. See the readme and wiki for more information.
A PropertyChangedNotificationInterceptor needs to be implemented. They provide an example for WPF in the wiki; my implementation for a WinForms project:
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();
}
}
}
Set PropertyChangedNotificationInterceptor.UIContext
somewhere. You can put PropertyChangedNotificationInterceptor.UIContext = WindowsFormsSynchronizationContext.Current;
in the constructor of your main form.
The Form
constructor goes through the Control constructor and will eventually end up creating the WindowsFormsSynchronizationContext
if one does not already exist. Therefore, it is safe to capture the .Current
here. (Note: this might be an implementation detail, and could change in future .NET versions, different platforms, etc.)
Keep in mind that this only works if you only have a single sync context (a single UI thread). If you ever get into the mess of multiple UI threads, and require data-binding on more than one, this would get far more complicated. So please don't do that.
Thanks to Simon Cropp for writing PropertyChanged
and also helping me find this particular feature of it.
If you are not lucky and have to use Winforms try using MainForm of your application to invoke handler in UI thread. The bad thing is that you have to include
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));
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With