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.
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://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
3 answers
Sort by: Most helpful
-
Peter Fleischer (former MVP) 18,371 Reputation points
2020-04-01T15:47:42.623+00:00 -
Sebastian Möller 6 Reputation points
2020-04-01T17:44:26.927+00:00 Hi Peter,
why only if you don't consume it in your code? Experimental testing, or any official .Net team statement?
Darius Geiß 1 Reputation point2020-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
-
Peter Fleischer (former MVP) 18,371 Reputation points
2020-04-03T09:50:05.74+00:00 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
Darius Geiß 1 Reputation point2020-04-03T11:25:26.787+00:00 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.
Peter Fleischer (former MVP) 18,371 Reputation points2020-04-03T12:30:32.38+00:00 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
Darius Geiß 1 Reputation point2020-04-03T13:09:30.62+00:00 As you can see lbProt.Items.Add causes the exception. lbProt.Items is the UI bound collection. The call of the prot method comes from OnPropertyChanged.
This is the spot where your code changes a UI bound collection.
Peter Fleischer (former MVP) 18,371 Reputation points2020-04-03T13:25:17.4+00:00 Hi Darius, please, read the question of OP! My posted code demonstrate that you can raise PropertyChanged from non UI thread if your code haven't access to non thread-saved UI elements. If you have access you must raise PropertyChanged via dispatcher:
Friend Sub Execute(par As Integer) Thread.Sleep(par) LinkName = LinkName.Replace("noready", "ready") Ready = True ' comment Invoke out for test without UI dispatcher uiDispatcher.Invoke(New Action(Sub() OnPropChanged(NameOf(LinkName)) OnPropChanged(NameOf(Ready)) End Sub)) End Sub
Darius Geiß 1 Reputation point2020-04-03T15:20:08.153+00:00 After having a closer look at the exception I now think that I understand what your point is.
It is save to update UI bounded properties from a background thread.
But its not allowed to access UI elements from a background thread. See example of InvalidOperationException:
Public Class Window97 Public Sub New() ' This call is required by the designer. InitializeComponent() Task.Run(Sub() prot("test")) End Sub Private Sub prot(msg As String) Dim x As Cursor = Me.Cursor End Sub End Class
Am I right?
So getting back to the question. If he uses the background threads just to change the values of the properties, that are bound to the UI from e.g. a view model. And if he doesn't access the UI elements from a background thread, than everything is fine. Is that correct?
Peter Fleischer (former MVP) 18,371 Reputation points2020-04-04T05:51:47.223+00:00 But its not allowed to access UI elements from a background thread.
Not just the UI objects, but also all other non-thread-safe objects (inherited from DispatcherObject) that were instantiated in the UI thread.
see Threading Model:
https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/threading-modelPropertyChanged only triggers (start) the thread for managing the UI.
If he uses the background threads just to change the values of the properties, that are bound to the UI from e.g. a view model. And if he doesn't access the UI elements from a background thread, than everything is fine. Is that correct?
Not only UI elements, objects inherited from DependencyObject too, like CollectionViewSource. It's impossible create (instantiate) CollectionViewSource in background thread and then bind to UI in foreground thread. Every cross thread access to non-tread-save objects results in error.
Sign in to commentPeter Fleischer (former MVP) 18,371 Reputation points2020-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 No comments
-
Hi Sebastian, try this example:
I can you post an example.
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?
Hi Sebastian,
When you inform the UI, the PropertyChanged event automatically triggers the start of the refreshing process. This process will be executed in the UI thread. You don't need to use Invoke to marshall it explicitly.
Note that it is only true for change notifications on scalar properties (i.e. PropertyChanged event). Collection change notifications (INotifyCollectionChanged.CollectionChanged event) don't work that way, they must be raised on the UI thread manually.
Own code in the event routine of PropertyChanged is executed in the thread in which PropertyChanged was raised. An error is occured when objects that are not thread-safe are accessed in this event routine (e.g. UI controls).
For posting demo code my account is blocked:
WAF v2 has determined your request exceeded the normal web request and has blocked your request.
Sign in to comment