Dispatcher required for changing WPF bound properties?

Sebastian Möller 6 Reputation points
2020-04-01T12:18:43.747+00:00

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://learn.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 Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,647 questions
0 comments No comments
{count} vote

3 answers

Sort by: Most helpful
  1. Peter Fleischer (former MVP) 19,056 Reputation points
    2020-04-01T15:47:42.623+00:00

    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.

    2 people found this answer helpful.

  2. Darius Geiß 1 Reputation point
    2020-04-03T08:42:21.827+00:00

    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
    

  3. Peter Fleischer (former MVP) 19,056 Reputation points
    2020-04-02T19:23:46.347+00:00

    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
    
    0 comments No comments