How to propagate changes across threads

I hope you didn’t get your hopes up too much when you read the title for this post. This time, instead of showing off what we support in our platform, I will explain to you what we don’t. The scenario I have in mind is when you want to bind a control in the UI thread to a collection or property that is modified in a different worker thread. My goal for this post is to tell you what works today and what doesn’t, and although no good workaround exists, I will discuss some ideas around this topic.
Here is the quick version of this post:
- We do not support collection change notifications across threads.
- We support property change notifications across threads.
Now the long version:
(Disclaimer: I will try to make the content as easy to read as possible, but you will only take full advantage of this post if you’re comfortable with multithreading, the Avalon Dispatcher and some basic data binding.)
Collection change notifications
In this scenario, I have a ListBox that is data bound to a collection of Place objects:
<ListBox Name="lb"/>
<Button Click="Throw_Click">Throw</Button>
ObservableCollection<Place> throwPlaces;
private void Throw_Click(object sender, RoutedEventArgs e)
{
throwPlaces = new ObservableCollection<Place>();
AddPlaces(throwPlaces);
lb.ItemsSource = throwPlaces;
lb.DisplayMemberPath = "Name";
(…)
}
private void AddPlaces(ObservableCollection<Place> places)
{
places.Add(new Place("Seattle", "WA"));
places.Add(new Place("Redmond", "WA"));
places.Add(new Place("Bellevue", "WA"));
(…)
}
Pretty simple. Next, I want to make sure that any changes to my collection are propagated to the UI. Typically, if you are using ObservableCollection<T>, this comes for free because it already implements INotifyCollectionChanged. However, this time I want to change the collection from a different thread:
Thread workerThread1;
private void Throw_Click(object sender, RoutedEventArgs e)
{
(…)
workerThread1 = new Thread(new ThreadStart(CrashMe));
workerThread1.Start();
}
void CrashMe()
{
throwPlaces.RemoveAt(0);
}
Unfortunately, this code results in an exception: “NotSupportedException - This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.” I understand this error message leads people to think that, if the CollectionView they’re using doesn’t support cross-thread changes, then they have to find the one that does. Well, this error message is a little misleading: none of the CollectionViews we provide out of the box supports cross-thread collection changes. And no, unfortunately we can not fix the error message at this point, we are very much locked down.
If you understand the Avalon Dispatcher, you’re probably already working on a master plan to delegate all collection change operations to the UI thread. You can do this by deriving from ObservableCollection, making sure your constructor takes a dispatcher as a parameter, and overriding all collection change operations. Here is my implementation of this collection:
public class BeginInvokeOC<T> : ObservableCollection<T>
{
private Dispatcher dispatcherUIThread;
private delegate void SetItemCallback(int index, T item);
private delegate void RemoveItemCallback(int index);
private delegate void ClearItemsCallback();
private delegate void InsertItemCallback(int index, T item);
private delegate void MoveItemCallback(int oldIndex, int newIndex);
public BeginInvokeOC(Dispatcher dispatcher)
{
this.dispatcherUIThread = dispatcher;
}
protected override void SetItem(int index, T item)
{
if (dispatcherUIThread.CheckAccess())
{
base.SetItem(index, item);
}
else
{
dispatcherUIThread.BeginInvoke(DispatcherPriority.Send, new SetItemCallback(SetItem), index, new object[] { item });
}
}
// Similar code for RemoveItem, ClearItems, InsertItem and MoveItem
(…)
}
When you create this collection, make sure you pass the dispatcher from the UI thread as a parameter to the constructor. Now imagine you change this collection from a worker thread. The first time SetItem is called, CheckAccess will return false because we are not in the UI thread. We will then add a call to this same method to the UI thread’s dispatcher queue, at priority Send. Once the dispatcher finishes processing the current job (and any other higher priority jobs), it picks up the one we added and SetItem is called again, this time on the UI thread. CheckAccess is called again, but this time it returns true, and we call SetItem on the collection. In plain english, this code means “Use the UI thread to set an item in the collection.”
Here is the code that uses this collection:
<ListBox Name="lb"/>
<Button Click="DelegateUIThread_Click">DelegateUIThread</Button>
BeginInvokeOC<Place> beginInvokePlaces;
private void DelegateUIThread_Click(object sender, RoutedEventArgs e)
{
beginInvokePlaces = new BeginInvokeOC<Place>(lb.Dispatcher);
AddPlaces(beginInvokePlaces);
lb.ItemsSource = beginInvokePlaces;
lb.DisplayMemberPath = "Name";
workerThread1 = new Thread(new ThreadStart(DontCrashMe));
workerThread1.Start();
}
void DontCrashMe()
{
beginInvokePlaces.RemoveAt(0);
}
If you click this button, you will see that the data will actually be changed (the item at index 0 will be removed) without any crashes. This will probably work OK if you only have the UI thread and a worker thread, but it may get you into trouble if you have two or more worker threads. This has nothing to do with Avalon, it’s just a plain multithreading problem. Let’s take a look at this same solution, but with two worker threads this time:
private void DelegateUIThreadNotWorking_Click(object sender, RoutedEventArgs e)
{
beginInvokePlaces = new BeginInvokeOC<Place>(lb.Dispatcher);
AddPlaces(beginInvokePlaces);
lb.ItemsSource = beginInvokePlaces;
lb.DisplayMemberPath = "Name";
workerThread1 = new Thread(new ThreadStart(DelegateUIThreadNotWorking_Thread1));
workerThread1.Start();
workerThread2 = new Thread(new ThreadStart(DelegateUIThreadNotWorking_Thread2));
workerThread2.Start();
}
void DelegateUIThreadNotWorking_Thread1()
{
int count = beginInvokePlaces.Count;
Thread.Sleep(500); // do a bunch of work (or be really unlucky to be interrupted by another thread here)
Place newPlace = beginInvokePlaces[count - 1];
}
void DelegateUIThreadNotWorking_Thread2()
{
Thread.Sleep(100); // do a little work
beginInvokePlaces.RemoveAt(0);
}
Look at the DelegateUIThreadNotWorking_Thread1() method. If you are unlucky enough to have execution switch from thread 1 to thread 2 between the calculation of the count and the use of indexer, and if you’re even more unlucky to have thread 2 change your collection, you’re in trouble. In this particular scenario, count is initially 11 in thread 1, then thread 2 removes an item and it becomes 10. However, when execution goes back to thread 1, the indexer still thinks count is 11, and will look for the item in index 11 - 1, which will throw an ArgumentOutOfRangeException. In a real world scenario, the probability of this happening would increase with the amount of work you would do in place of the Thread.Sleep(500) call.
If we’re getting synchronization problems, the next logical step is to lock any atomic operations on these threads. Here is how I did that:
<ListBox Name="lb"/>
<Button Click="LockingOperations_Click">LockingOperations</Button>
InvokeOC<Place> invokePlaces;
object lockObject;
public Window1()
{
InitializeComponent();
lockObject = new object();
}
private void LockingOperations_Click(object sender, RoutedEventArgs e)
{
invokePlaces = new InvokeOC<Place>(lb.Dispatcher);
AddPlaces(invokePlaces);
lb.ItemsSource = invokePlaces;
lb.DisplayMemberPath = "Name";
workerThread1 = new Thread(new ThreadStart(LockingOperations_Thread1));
workerThread1.Start();
workerThread2 = new Thread(new ThreadStart(LockingOperations_Thread2));
workerThread2.Start();
}
void LockingOperations_Thread1()
{
lock (lockObject)
{
int count = invokePlaces.Count;
Thread.Sleep(500); // do a bunch of work
Place newPlace = invokePlaces[count - 1];
}
}
void LockingOperations_Thread2()
{
lock (lockObject)
{
Thread.Sleep(100); // do a little work
invokePlaces.RemoveAt(0);
}
}
Because I am locking all atomic sequences of operations and all changes to the collection, I know that the logic in the worker threads will never lead to the synchronization problem in the previous example. The code that does the item generation in ItemsControl has a handle to the collection, but I can tell you for sure that it never modifies the collection (it only reads it), so there should be no conflicts with the UI thread either. I also couldn’t think of a scenario where this code would lead to a deadlock (although it’s easier to prove the existence of a deadlock than the lack of one…) There was one possible problem I was able to identify: if the last operation of a locked block does a BeginInvoke to the UI thread, but the execution is transfered to the other worker thread before that operation is able to execute, we could get in a bad state. I solved this by replacing all BeginInvoke calls (asynchronous) with Invoke calls (synchronous). This way, we guarantee that, by the time we exit the lock on one thread, all operations inside that lock have finished executing in the UI thread.
This solution sounds pretty good, but I can think of a couple of reasons why you should NOT change your million dollar application to use it:
- Imagine the current job in the dispatcher is a lengthy layout pass, followed by several input operations (which have high priority). The dispatcher will not interrupt the current job, not even for a higher priority job, so we will have to let the layout pass finish. Also, since the worker threads are delegating to the dispatcher with priority Send, we will have to wait for all higher priority dispatcher items before the change operations are allowed to run. Delegating the worker thread operations at a priority higher than Send is not a good idea because your UI may become unresponsive. Basically, the worker thread needs to wait for the UI to catch up, and this is not efficient.
- It hasn’t been tested. I make absolutely no guarantees about a solution that I have only seen running on my machine.
If you do decide to go ahead and use this solution (at your own risk), there are a few things for you to keep in mind:
- You should never add thread-bound objects to the ObservableCollection (such as UIElements). This solution can only be used with your usual data items (and frozen Freezables) because they are not thread-bound.
- The one advantage this solution provides is parallelism between the UI thread and one of the worker threads. Because the UI thread doesn’t take any locks, it can be running at the same time as one other thread that takes locks. This is a big advantage if you have lengthy computations in the place of the Sleep calls that don’t require delegating to the UI thread. However, if most of the work you do in the worker threads is collection change operations (which delegate to the UI thread), then this solution will not provide any advantage to you. If this is your scenario, you should start by asking yourself whether you really need a multithreaded solution. If you realize you do need it, then you should consider delegating the sequence of change operations as a whole, instead of delegating one by one like in my solution.
- With great power comes great responsibility. Feel free to use the ObservableCollection that delegates all operations to the UI thread, but you are still responsible for locking all critical operations.
We really wanted to make it easier to develop multithreaded applications that use data binding, but unfortunately we ran out of time in V1. We do realize that it shouldn’t be so complex. Hopefully we will be able to revisit this topic in V2.
Property change notifications
Property change notifications across multiple threads work pretty well. When a UI element is data bound to a property that gets changed by a worker thread, Avalon will receive notification of the property change on the UI thread. One thing that may surprise you is that if there are many property changes happening very quickly in your data source, Avalon won’t update your target dependency property at the same rate. This was a conscious decision. Although this behavior may prevent you from creating an accurate graph of all data changes over time, it has the advantage of keeping the UI responsive. The UI will always get updated when the data has changed, just not for every single change.
This is common sense, but I’ll mention it anyway: if your setter is not atomic, don’t forget to use a lock around your operations. Typically this is not a problem because most setters are atomic.
Talking about the one work item I so wished we had finished for V1 is tough. Thanks to all the customers who have asked me this question in the past. Thanks to Ian Griffiths for getting me to stop procrastinating and write this blog post about it. Thanks to David Jenni and Dwayne Need for listening to me ramble about multithreading when they had better things to do. Thanks to Sam Bent and Eric Stollnitz for going the extra mile of reviewing my sample code.
The screenshot for today’s post isn’t all that interesting, but here it is anyway:
Here you can find the VS project with this sample code. This works with RC1 WPF bits.
Mitsu
Good sample once again Bea. I can notice something nice in your code. When a conflict occurs, a managed exception is raised ! Modifying the UI from a separated thread in win32 apps create hazardous bugs that crash the process. Thanks to WPF, we now live in a more secure .Net world where everything can be catched.
Even if multithreaded scenarios remain complicated, this is really a very nice change.
Mitsu, waiting for the Concur project..
September 24, 2006 at 3:28 am
Bea
Thanks for your comment Mitsu. I hope all is going well for you in MS France.
September 24, 2006 at 1:25 pm
Sam
This got nothing to do with threads, sorry for intruding, but I don’t know where to ask this question (managed newsgroups provide no answer)
When you navigate back in a browser-like WPF application, the pages are re-created (at least the constructor is called).
And somehow, magically, the content for Textbox(es) in the page will be filled with whatever had been in there when the page had been left.
Since the page is created anew on navigating ‘Back’, where does this content come from? Is it bound to some kind of session storage as default?
And how do I get my own values back (as content for Listboxes for example are lost on navigating)?
thanks, Sam!
September 25, 2006 at 6:01 am
Bea
Hi Sam,
That question is a little bit outside of my area of expertise, but I mentioned it to Lauren, and she said she will blog about this next.
Sorry no one replied to you in the newsgroups.
Thanks,
Bea
September 25, 2006 at 11:04 am
Lauren
Sam, I posted a response to your question here: http://laurenlavoie.com/avalon/191
I also posted a follow-up question for you: I did not understand the last part of your question, about ListBox. Could you elaborate or send me an example of some source code that is not behaving as expected?
Thanks!
Lauren
http://laurenlavoie.com
September 26, 2006 at 4:04 pm
Sam
Bea,
I just stumbled upon Laurens post, thanks a lot to you two for your support!
I’ll post further questions/stuff on this part on Laurens Post.
Thanks a lot!
Sam
September 27, 2006 at 7:47 am
YesKay
QUESTION: Button on a ListView row and animation.
Every row in my ListView has ‘select’ button. I would like to make the size of the button bigger on MouseOver event.
The bitmap effects are working as expected. But setting the width and height property is not working.
Looks like, the row settings of the ListView are overriding these setter properties.
Is there any way I can work around this and make those buttons bigger when user hovers mouse on it.
September 29, 2006 at 5:14 pm
Bea
Hi YesKay,
I am able to animate a Button’s Width inside a ListView. You can find my sample here. Is this what you were trying to do?
Let me know.
Thanks,
Bea
September 11, 2007 at 1:20 am
Sam
Bea, somewhere down in my ‘unresolved’ stuff I found a question regarding a problem using Databinding:
Assume a simple Page, containing just one Textbox and one Button.
The Textbox is bound to some data item. The Button is the default Button of the Page.
Now enter the Text “Hello World” in the Textbox, use the mouse to click the button: everythings fine, just like it should be: reading the variable does return “Hello World” just like it should.
Go back, Change “Hello World” to “Hello Bea!”, press ENTER *without leaving the Textbox* and everythings a mess: reading the variable still returns “Hello World” instead of “Hello Bea!” since the source had not been Updated.
Very nasty in my opinion! Is this behaviour known and accepted? Or is it an unknown bug?
Oh, and any idea for a workaround?
September 30, 2006 at 6:55 am
Bea
Hi Sam,
Currently, updating the data from the target to the source is controlled by a property called “UpdateSourceTrigger” on Binding. There are 3 possible values for this property: LostFocus, Explicit or PropertyChanged. By default, this property is set to LostFocus, which means the data is only transfered from the TextBox to the source when the TextBox loses focus.
You can certainly change this behavior. If you want the data to be updated everytime you click on a button (by going there with the mouse, pressing enter, etc), you probably want to set your UpdateSourceTrigger to Explicit. Then, in the button’s Click handler, you can call the UpdateToSource method of BindingExpression to update the data.
This is our story for V1. We are planning to do some improvements around this area for V2, so it may be that this scenario will be improved. We’ve discussed before implementing what you expected (which would be consistent with WinForms): when the Default button on the page is clicked, all bindings are updated to the source. It’s definetely a possibility we’ll go in this direction.
Thanks for your feedback. We are now starting to plan V2, so any feedback around features you guys may have is very welcome, and very timely.
Thanks,
Bea
September 30, 2006 at 11:03 am
Sam
Hi Bea,
explicit updating the source is ok for my cute ‘lil exsample with one TextBox, but for a page with a lot of TextBox, ComboBox and Whatnot this is very much work to add an explicit updatesource for every single XAML-UI-item in every button that might be invoked by a keyboard shortcut.
Additionally it is very error-prone, since it is easy to forget the updatesource for a Textbox in a click-handler when changing something in the UI/XAML.
A workaround might be to call MyDefaultButton.Focus() whenever a click event is invoked (by keyboard). This is ugly, too, but at least not as easy to forget.
Hope this will get better next release!
Sam
October 2, 2006 at 6:49 am
Bea
Sam,
Yes, I completely agree with you about this solution not scaling well. That is why we will discuss this scenario again for V2, and hopefully we will be able to improve it.
Thanks for your feedback,
Bea
October 3, 2006 at 9:15 am
Sheva
Hi, Beatriz, you may think I am bit pedantic, but I want to point out one mistake you made in your orginal article:
you said:”we will then add a call to this same method to the UI thread’s dispatcher queue, at priority Send. Once the dispatcher finishes processing the current job (and any other higher priority jobs)”.
actually DispatcherPriority.Send is the highest possible priority, so if there is no work item which is marked with Send priority, and which is added to the queue before our data updating worker item, our worker item should be proccessed immediately after the UI thread finishes processing the current running worker item.
Sheva
October 4, 2006 at 4:41 pm
Bea
Hi Sheva,
Good catch, you are correct. Other work items of priority Send that were added previously to the dispatcher will be executed before ours, but no higher priority work item exists.
Thanks for pointing it out.
Bea
October 5, 2006 at 9:18 am
Brett
Bea,
I’ve been busy working on other parts of .NET 3.0, but I am back on WPF. I’m sorry to pollute your comments, but like Sam I have an off topic question for which I am not able to find an answer (asked on WPF forums with no takers). If you can help, I would greatly appreciate it.
Is there any support for creating DataTemplates where the DataType is generic? Using the MyClass format creates invalid XML, and MyClass`1[[System.Int32, etc.]] seems a bit of a stretch. Is this something that is simply not possible in XAML?
Thanks in advance,
Brett
October 5, 2006 at 3:34 pm
Bea
Hi Brett,
Welcome back to WPF.
I am not an expert on the parser, but as far as I know there is no escaping mechanism that allows you to do that. I think it’s not possible to do what you want, for V1.
Bea
October 6, 2006 at 10:48 pm
Brett
Hi Bea,
Thanks for responding! My thread on the forums still goes unanswered.
Best,
Brett
October 7, 2006 at 2:03 pm
Anonymous
You could also use the Avalon supplied SynchronizationContext from the UI thread to accomplish the Send/Post of worker delegates to the UI thread. I am not sure if it accomplishes much in this situation apart from changing the type of the constructor argument from Dispatcher to SynchronizationContext, eliminating the ability to set priority (it uses Normal priority) and eliminating the kind of ugly Dispatcher.BeginInvoke call. It could gain you a more generic type of collection, one in which constructor arguments could dictate the threading context in which operations occured, so by creating it with the Avalon supplied SynchronizationContext all operations would happen on the UI thread and by supplying a default one all operations would either occur on the invoking thread or on a threadpool thread (depending on if you did a Post or Send).
October 8, 2006 at 5:15 pm
Kent boogaart
Hi Beatriz,
Great post. I thought you might be interested in my take on the subject: http://kentb.blogspot.com/2006/11/cross-thread-collection-binding-in-wpf.html
November 9, 2006 at 2:59 am
Bea
Hi Kent,
Once again, I love seing people thinking about the same issues I think about and coming up with alternative approaches. I’m a big fan of solutions that improve the separation of UI and data - I love the improvements in your solution!
(And btw, yes, it is *SHE* and not he
)
Bea
November 10, 2006 at 1:10 pm
Bea
Hi Kent,
Sam Bent (from the data team) and I see at least one serious bug in your solution and in Chong’s solution (http://itchong.blogspot.com/2007/01/how-can-i-propagate-changes-across.html). It applies to all operations, but let’s take Insert as an example. Your method inserts the new item into the data structure on the worker thread, then raises the change notifcation on the dispatcher thread (blocking the worker thread until the dispatcher thread is available). So there’s an interval of time where the item has been added, but the dispatcher thread doesn’t know it yet. If the dispatcher thread reads the collection during this interval, it will get the wrong information. This can lead to incorrect display, or even a crash.
The solution in my blog does both the actual insertion and the notification on the dispatcher thread, so it doesn’t have the problem.
November 15, 2006 at 5:36 pm
tony
Bea,
I guess your email box would get full often and quickly if you published it.
But (sigh) that leaves me with no other option but to comment here.
I asked this question in many forums and never got any response so you are my last hope.
Basically, I want to create a new dependency property (DP), the value of which would depend on calculation involving several other DPs (custom or existing).
For ex. custom Canvas DP called Center would depend on Canvas’ existing Top, Left, Width and Height (or ActualWidth and ActualHeight) DPs.
Like: Center.X=Left+ActualWidth/2.0
I want to bind to this new Center DP and be sure that, whatever changes Canvas goes through, dragging, resizing, scaling, rotation, re-layout, ecc. it will always update the target with fresh Center coordinates.
What is the best way to do that without resorting to MultiBinding and Converters (if possible)?
November 20, 2006 at 6:18 pm
Bea
Hi Tony,
There are a few ways you can do that.
1) You can override the metadata of the DPs that affect your new DP and add a change handler that changes your new DP too. However, I can’t get this solution to work with your particular scenario because ActualWidth and ActualHeight are read only. I don’t know if it’s possible to override metadata of read only properties - I had trouble doing that.
2) You can override the PropertyChanged method and, if the DPs passed in the event args are ActualWidth or ActualHeight, you recalculate Center.
3) You can override OnRenderSizeChanged and recalculate Center within this method. This is the solution I implemented, which you can find here.
Here is the part of the code that matters:
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
this.MyCenter = new Point(sizeInfo.NewSize.Width / 2.0, sizeInfo.NewSize.Height / 2.0);
}
Thanks,
Bea
November 21, 2006 at 8:24 pm