Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to be thread-safe using INotifyPropertyChanged with no SynchronizationContext?

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));
    }
like image 599
Stephen Gilboy Avatar asked Oct 19 '25 01:10

Stephen Gilboy


2 Answers

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.

  1. First, install PropertyChanged.Fody, available from NuGet.

  2. 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.

  3. 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();
            }
        }
    }
    
  4. 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.

like image 186
Bob Avatar answered Oct 21 '25 14:10

Bob


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)); 
        }              
    }
}
like image 41
Nai Avatar answered Oct 21 '25 13:10

Nai



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!