question

SebastianMller-8129 avatar image
1 Vote"
SebastianMller-8129 asked ·

Dispatcher required for changing WPF bound properties?

Hello,
From time to time I read some articles that state that if WPF binds to a property that implements INotifyProperyChanged, such property can be changed from any thread without a dispatcher, because WPF would automatically dispatch the INotifyProperyChanged event to the UI-thread.
On the other hand, all Microsoft samples that I found are still using the dispatcher even for INotifyProperyChanged implementing properties.

To be very clear here, I'm not talking about collections, nor dependency properties.

Some links that talk about such "auto-dispatching" are:
https://stackoverflow.com/questions/8994714/updating-bound-properties-from-a-background-thread
https://docs.microsoft.com/en-us/archive/msdn-magazine/2014/april/mvvm-multithreading-and-dispatching-in-mvvm-applications (2nd paragraph under 'Dispatching in MVVM applications')
https://metashapes.com/blog/not-shooting-foot-wpf-best-practices/ (Item #4)

If this would be true, it would ease programming a lot.
Currently, I'm dispatching everything, and I would only change this if I can be sure that this is an official feature and will still work on .Net 5.

So, any information, especially official sources, are highly appreciated.
Thanks, Sebastian

windows-wpf
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

PeterFleischer-3316 avatar image
2 Votes"
PeterFleischer-3316 answered ·

Hi Sebatian, starting with .NET 4.5 I use NotifyPropertyChanged in non-UI-thread without dispatching to inform the UI for refreshing, only if I don't consume PropertyChanged event in my own code.

· 4 · Share
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi Peter,
why only if you don't consume it in your code? Experimental testing, or any official .Net team statement?

0 Votes 0 · ·

Hi Sebastian, try this example:

  1. data object raise PropertyChanged

  2. UI element bind list of data objects

  3. changed values in data object raise PropertyChanged

  4. additional for each data object in list the PropertyChanged event is subscribed in your code

  5. in your code in event receiver you write value in UE element.

I can you post an example.

0 Votes 0 · ·

Yes please. I tried myself, but even with own registration it is still working.
Do you know any official Microsoft document stating that it is allowed to change such properties without dispatching?

0 Votes 0 · ·
Show more comments
PeterFleischer-3316 avatar image
0 Votes"
PeterFleischer-3316 answered ·

Hi Sebastian,

try following demo with and without comments.

XAML:

 <Window x:Class="Window97"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:local="clr-namespace:WpfApp1"
         mc:Ignorable="d"
         Title="Window97" Height="450" Width="800">
   <StackPanel>
     <ListBox ItemsSource="{Binding View}" Height="200">
       <ListBox.ItemTemplate>
         <DataTemplate>
           <TextBlock>            
             <Hyperlink NavigateUri="{Binding NaviUri}" IsEnabled="{Binding Ready}">
               <Run Text="{Binding LinkName}"/> 
             </Hyperlink>
           </TextBlock>
         </DataTemplate>
       </ListBox.ItemTemplate>
     </ListBox>
     <ListBox x:Name="lbProt" Height="200"/>
   </StackPanel>
 </Window>

and code:

 Imports System.Collections.ObjectModel
 Imports System.ComponentModel
 Imports System.Runtime.CompilerServices
 Imports System.Threading
 Imports System.Windows.Threading
    
 Public Class Window97
   Public Sub New()
    
     ' This call is required by the designer.
     InitializeComponent()
    
     ' Add any initialization after the InitializeComponent() call.
     Dim vm As New Window97VM(AddressOf prot)
     Me.DataContext = vm
   End Sub
    
   Private Sub prot(msg As String)
     Me.lbProt.Items.Add(msg)
   End Sub
 End Class
    
 Public Class Window97VM
    
   Private rnd As New Random
   Public Sub New(p As Action(Of String))
     For i = 1 To 100
       Dim d As New Window97Data With {.LinkName = $"Link {i} noready", .NaviUri = "http://www.microsoft.com", .Ready = False}
       AddHandler d.PropertyChanged, Sub(sender, e)
                                       ' comment this out for test without UI dispatcher
                                       p($"NotifyPropertyChanged in {e.PropertyName}")
                                     End Sub
       col.Add(d)
     Next
     cvs.Source = col
     '
     ThreadPool.SetMaxThreads(3, 3)
     For Each item As Window97Data In col
       ThreadPool.QueueUserWorkItem(Sub(state)
                                      item.Execute(CType(state, Integer))
                                    End Sub, rnd.Next(500, 2000))
     Next
   End Sub
    
   Private col As New ObservableCollection(Of Window97Data)
   Private cvs As New CollectionViewSource
    
   Public ReadOnly Property View As ICollectionView
     Get
       Return cvs.View
     End Get
   End Property
    
 End Class
    
 Public Class Window97Data
   Implements INotifyPropertyChanged
   Public Property LinkName As String
   Public Property NaviUri As String
   Public Property Ready As Boolean
    
   Private uiDispatcher As Dispatcher = Dispatcher.CurrentDispatcher
    
   Friend Sub Execute(par As Integer)
     Thread.Sleep(par)
     LinkName = LinkName.Replace("noready", "ready")
     Ready = True
     ' comment tis out for test without UI dispatcher
     'uiDispatcher.Invoke(New Action(Sub()
     OnPropChanged(NameOf(LinkName))
     OnPropChanged(NameOf(Ready))
     'End Sub))
   End Sub
    
   Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
   Private Sub OnPropChanged(<CallerMemberName> Optional propName As String = "")
     RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))
   End Sub
 End Class


· Share
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

DariusGei-6146 avatar image
0 Votes"
DariusGei-6146 answered ·

Hello PeterFleischer,

I checked your code and found the reason for the InvalidOperationException. It has nothing to do with the PropertyChanged, that you throw from a background thread. The reason for the exception is, that you are trying to modify a UI bound collection from a background thread (see codebehind line 19).

If you want to modify a UI bound collection from a background thread, than you have to use either the UI dispatcher or BindingOperations.EnableCollectionSynchronization.

Here is the modified Window97 class:

 Public Class Window97
    
     Private uiDispatcher As Dispatcher = Dispatcher.CurrentDispatcher
    
     Public Sub New()
    
         ' This call is required by the designer.
         InitializeComponent()
    
         ' Add any initialization after the InitializeComponent() call.
         Dim vm As New Window97VM(AddressOf prot)
         Me.DataContext = vm
     End Sub
    
     Private Sub prot(msg As String)
         uiDispatcher.Invoke(New Action(Sub()
                                            Me.lbProt.Items.Add(msg)
                                        End Sub))
     End Sub
 End Class
· 8 · Share
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi, in my demo I demonstrate the problem of op!

If you use PropertyChanged in own code and access in this event method any UI element you must raise raise PropertyChanged via dispatcher:

    Friend Sub Execute(par As Integer)
      Thread.Sleep(par)
      LinkName = LinkName.Replace("noready", "ready")
      Ready = True
      ' comment tis out for test without UI dispatcher
      uiDispatcher.Invoke(New Action(Sub()
        OnPropChanged(NameOf(LinkName))
        OnPropChanged(NameOf(Ready))
      End Sub))
    End Sub
0 Votes 0 · ·
DariusGei-6146 avatar image DariusGei-6146 PeterFleischer-3316 ·

No that is wrong.

Your PropertyChanged handler

 AddHandler d.PropertyChanged, Sub(sender, e)
                                        ' comment this out for test without UI dispatcher
                                        p($"NotifyPropertyChanged in {e.PropertyName}")
                                      End Sub

works as expected.

The code that causes the exception is the

 Private Sub prot(msg As String)
      Me.lbProt.Items.Add(msg)
    End Sub

which is called in the PropertyChanged handler. Within the PropertyChanged handler p is prot and prot changes a UI bound collection. This isn't allowed. If you modify prot to just change a UI bound property everything will work as expected, even if you use a background thread. But ,if you want add items to a UI bound collection (as you do), than you have to do it using the dispatcher or BindingOperations.EnableCollectionSynchronization.

0 Votes 0 · ·

Hi, BindingOperations.EnableCollectionSynchronization. is necessary if the collection changed. In my demo collection doesn't be changed from other thread. Only values of data items are changed in non UI thread.

 1. Dim vm As New Window97VM(AddressOf prot) ' UI thread
 2. Private col As New ObservableCollection(Of Window97Data) ' UI thread
 3. Dim d As New Window97Data ' UI Thread
 4. Private uiDispatcher As Dispatcher = Dispatcher.CurrentDispatcher ' save UI dispatcher
 5. ThreadPool.QueueUserWorkItem(Sub(state) ' in non UI thread executed Sub
   item.Execute(CType(state, Integer))  ' manipulate data item in non UI thread
 6. uiDispatcher.Invoke(New Action(Sub()
   OnPropChanged(NameOf(LinkName)) ' raise PropertyChanged in UI thread
   OnPropChanged(NameOf(Ready))
 7. p($"NotifyPropertyChanged in {e.PropertyName}") ' invoke delegate in UI thread
 8. Me.lbProt.Items.Add(msg) ' access lbProt in UI thread


0 Votes 0 · ·
Show more comments