Proposed INotifyPropertyChanged invocations

  • 21. března 2012 14:52
     
      Obsahuje kód

    My question relates to the actual invoking of the PropertyChanged
    event as it relates to getting onto the Dispatcher thread.  I've seen a
    variety of solutions out there.  Some people just say "don't change
    properties if you aren't on the UI thread", others add a reference to
    Dispatcher into the data object and push all invocations to that
    dispatcher.

    We thought of a variant that I haven't actually seen
    around, so I wanted to throw this out there to find out what we are
    missing!

    Basically, of the two options above, we thought the
    second was the better of the two (because then the caller does not have
    to be worried about what thread he is on).  But we didn't like the idea
    of pushing all invocations onto that dispatcher.  We came up with a
    number of scenarios (including implementing property dependencies) where
    an entity other than a WPF UI element would subscribe to the
    PropertyChanged event.  There is no reason that action needs to take up
    the UI thread.

    Instead, we break down the individual handlers
    one-by-one and determine if that one handler needs to be invoked to a UI
    thread.  In the various scenarios we have run into so far, each UI
    element data-bound inherits from DispatcherObject.  Based on that, we
    can determine a) does this particular invocation need to run against a
    dispatcher, and b) if so, against what dispatcher.

    It ends up looking like this:

                PropertyChangedEventHandler temp = PropertyChanged;
                if (temp != null)
                {
                    foreach (Delegate del in temp.GetInvocationList())
                    {
                        DispatcherObject target = del.Target as DispatcherObject;
                        if (target != null)
                        {
                            target.Dispatcher.BeginInvoke(del, DispatcherPriority.DataBind, this,
                                                     new PropertyChangedEventArgs(propName));
                        }
                        else
                        {
                            del.DynamicInvoke(this, new PropertyChangedEventArgs(propName));
                        }
                    }
                }

    Is there a reason we are missing as to why this is a BAD idea?

    Thanks,

Všechny reakce

  • 21. března 2012 15:32
     
     Navržená odpověď

    Actually, WPF already marshals events raised by an INotifyPropertyChanged implmentation to the correct Dispatcher
    thread. Try this:

    In XAML:

    <Window

        x:Class="BackgroundNotify.MainWindow"

        xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation

        xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml

        Title="MainWindow"
        Height
    ="100" Width="250">

      <Grid>

      <TextBlock

          Text="{Binding Text}" VerticalAlignment="Top"

          HorizontalAlignment="Left" Width="113" Margin="12,16,0,0" />

      </Grid>

    </Window>

    In code behind:

    using System.ComponentModel;

    using System.Threading;

    namespace BackgroundNotify

    {

        publicpartialclassMainWindow

        {

            publicMainWindow()

            {

                InitializeComponent();

                DataContext = newModel();

            }

            publicclassModel: INotifyPropertyChanged

            {

                publiceventPropertyChangedEventHandlerPropertyChanged;

                publicstringText { get; privateset; }

                publicModel()

                {

                    newThread(Update) {IsBackground = true}.Start();

                }

                privatevoidUpdate()

                {

                    varcount = 0;

                    while(true)

                    {                   
                        Thread.Sleep(1000);

                        Text = (++count).ToString();

                        if(PropertyChanged != null)
                        {
                            PropertyChanged(this, newPropertyChangedEventArgs("Text"));

                        }

                    }

                }

            }

        }

    }

    Run the app and you’ll see the TextBlock updating nicely
    even though PropertyChanged event is raised from another thread.

  • 21. března 2012 16:17
     
     

    This... confuses me.  I have run your code - I see that it works.  But I am almost certain when we first started implementing our MVVM code (a while back) that things like this blew up.

    In fact, a google search (INotifyPropertyChanged dispatcher) yields a number of articles/blogs on the topic and the need to dispatch from INotifyPropertyChanged to the UI thread:

    http://code.google.com/p/propertychanged/wiki/NotificationInterception

    "Often properties will be changed on a different thread to the UI. This is particularly common in Silverlight applications. Unfortunately changing the UI, which occurs when a databound property changes, is not supported in Silverlight or WPF. The workaround is to "dispatch" the property changed event to the UI thread."

    http://michlg.wordpress.com/2009/08/09/advanced-inotifypropertychanged-with-an-automatic-dispatcher/

    "In WPF it is really important that you do not fire the PropertyChanged and CollectionChanged Events from a different thread then its creator.
    Usually you have to check each time if you do not have to Invoke."

    http://www.ingebrigtsen.info/post/2010/01/16/Dispatcher-Safe-INotifyPropertyChanged.aspx

    "The main problem though with this is that it is running on a different thread than the UI thread and you can't modify anything on the UI without running on the same thread. This even goes for databinding, so if you have properties on an object databound in the UI and you set the value on any of these properties, you still need to be on the UI thread in order for this to work. This is also true for WPF applications. "

    So... has something changed in .NET at some point to alter this behavior? 

  • 21. března 2012 16:47
     
     

    Honestly I have no idea what they are talking about. I’ve always considered it a nice feature of WPF allowing the view model to be free threaded and decoupled from the Dispatcher. We have used it on a number of projects since .NET 3.5 at least.

    One thing to note is that the same is NOT true for the INotifyCollectionChanged interface. Its events must be serialized and marshaled to the correct UI thread by the developer. The reason is that unlike scalar properties a collection must remain unchanged from the time the notification event was raised until the view (WPF) has finished iterating through it.


  • 23. března 2012 4:32
    Moderátor
     
     

    Hi Cabadam,

    How about your concern, if your concern persists, please let us know.

    Best regards,


    Sheldon _Xiao[MSFT]
    MSDN Community Support | Feedback to us
    Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

  • 23. března 2012 15:52
     
     

    Yes and no - I see that it seems to be working from his test code.  But I KNOW this broke for us before - we didn't just add the behavior because we were bored.  And I doubt the various articles linked earlier (and at least one MVVM framework - Caliburn, see this page of the documentation) all implemented it this way if it was not actually needed.

    So, I'm rather nervous about removing this behavior from our codebase.  And if I am NOT removing it, that then goes back to my original question - is there anything wrong with this approach?

  • 23. března 2012 21:21
     
     
    I don't see anything wrong with your approach (except that I think it's not necessary). I've seen similar approaches aiming to overcome this limitation for INotifyCollectionChanged interface.
  • 23. března 2012 21:53
     
      Obsahuje kód

    I too find your approach novel but would have to experiment in lab with it.  I like what you are doing and would have never thought of it myself. 

    Here's something else you can think about...using the new and really cool TASK class.

      Task UITask= task.ContinueWith(() => 
        { 
         this.TextBlock1.Text = "Complete";  
        }, TaskScheduler.FromCurrentSynchronizationContext());

    You can spin off other threads using Task and do a continuation of that task on the GUI Thread!!!!

    How do you start and Continue a TASK?

    Task task = Task.Factory.StartNew(() => 
    { 
        DoLongRunningWorkOnOtherThread(); 
    
    }.ContinueWith()=>{
    
      thisTextBlock1.Text = SomeOtherThreadData;
    
    }, TaskScheduler.FromCurrentSynchronizationContext());


    JP Cowboy Coders Unite!

  • 23. března 2012 21:55
     
     
    P.S. You'll want to look into TASKs anyway because they are the new simple way to spin off threads AND utilize all CPUs...  So if you have a 64Way processor you could get up to 64 dedeicated CPUs for 64 TASKS.  I've tried this in lab and just couldn't believe the performance improvements.

    JP Cowboy Coders Unite!


  • 23. března 2012 21:57
     
     
    P.S. You can refactor the code above to put TASK stuff in MODELs, BINDINGS in VIEWMODELS, and VIEW stuff in VIEW.  If you use the TASK SyncContext you only need to send the PropertyChanged notification to WPF.

    JP Cowboy Coders Unite!