Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.Sign up
BindableCollection dispatches operations on the collection to the UI thread - why not just the change events? #247
we have a scenario where a BindableCollection is not only modified by user interaction (drag & drop of list items), but also may get many updates in short time from a background operation. In the latter case, performance is an issue and we set IsNotifying=false before the algorithm starts and re-enable change events afterwards.
That improved performance because it removed UI updates, but didn't make it great... We profiled the app and realized that a major performance killer is the fact that all operations on the collection (Add, Remove etc.) are dispatched to the UI thread via Execute.OnUIThread(...) in the BindableCollection implementation.
Question: Why so? Isn't it enough to have the overwritten OnCollectionChanged() and OnPropertyChanged() raise the change event on the UI thread?
We did such an implementation and it performs better and seems to work OK in our scenario. Are there any pitfalls with that approach which we oversee or would such an implementation be equally "correct" and faster in such scenarios?
changed the title from
BindableCollection dispatches all operations on the collection to the UI thread - why not just the change event?
BindableCollection dispatches operations on the collection to the UI thread - why not just the change event?
Nov 12, 2015
This code predates my taking over the project so I can't speak to the logic of why almost all the operations were wrapped in
I'd love to see a pull request with your implementation so we can discuss it further.
On a side note the implementation of
does anyone have any idea how to write tests for this change?
added a commit
Oct 5, 2016
I guess the fix up to here doesn't solve the problem but just flips it around:
The commit I sent before "fixes" this by doing anything except the Update-Notifications off the UI-Thread.
A better solution would be to create a specific thread to do the collection changes on.
The BindableCollection would take a distinct Manipulator-Thread. This Thread get's it's own Dispatcher.
This way I hope to solve both concerns, the one raised by @cdanne and the one solved by the original solution to always use the UI-Thread.
after discussion with @TeaDrivenDev and others today I came to the following concept to solve this issue:
The goal is to avoid (blocking) work on the UI thread, while keeping the collection manipulations thread safe.
My approach would be to use the same approach, but a different, dedicated thread to process the work.
It get's complicated when the other platforms are introduced, as there are three different Dispatcher implementations to target.
What i have locally yet consists of an extension to the IPlatformProvider by a method to get a Dispatcher
The BindableCollection then can get this dispatcher - and with it a dedicated thread that sleeps most of the time as long as nothing happens on the Collection.
My current problem is to get some kind of unified Dispatcher object with a unified API across the platforms that can be used for this.
Missing parts yet:
This feels like a lot of work for what should be a simple collection. The only real reason it exists is because a lot of the implementations of
The added complications of other platforms just get terrible.
Right now the bug issue is we're dispatching in a chatty way, rather than a chunky way, It may be best to keep the solution to just that.
Making it a completely thread safe collection while doing this can be a separate piece of work (with an associated discussion on whether it's necessary / better way to do it).
@nigel-sampson thanks for the comment, but I oppose: By the initial commit we don't "keep the solution to just that", but we introduce possible bugs that were not possible before.
But Sometimes having a good night isn't a bad thing, and talking to others isn't either.
Basically we came to the conclusion that the Dispatching stuff isn't even necessary to be thread save. All we want is not losing the concurrency-safety the BindableCollection had before (as that runs the risk to break existing code using the BindableCollection) while reducing the UI-Thread work.
By a simple lock we achieve the same goal across threads: A single lock (per collection instance) is used wrapping all manipulation commands of the base implementation.
As this locking is done on a single level only (by wrapping the base implementation), and there is only one lock object involved, it should be impossible to get a deadlock (deadlock always needs two interleaving locks).
Unit tests still working, but I want to do some more testing tomorrow on real-life code.
Nevertheless I appreciate any comments and remarks (except for the typo in the issue number in the commit message - realized that myself).
Oct 11, 2016
Looking over this some more with the view to release
I want to tackle this a different way. The first is something akin to what got brought up in #407 as a way to opt out of both
I think as evidenced here different people have different goals and opinions on what they want, especially when it comes to high performance updates.
What I propose is adding a new virtual method that will handle dispatching on both
This should allow customization in most scenarios to what people want to do, such as add a boolean property to turn it off at times, or there own custom thread dispatching.
referenced this issue
May 18, 2017
added a commit
May 22, 2017
added a commit
May 22, 2017
Are you suggesting to subclass PropertyChangedBase, Screen, Conductor etc and override OnUIThread for each one manually? If so it sounds very cumbersome to say the least.
IMHO this should be controlled centrally and affect all existing relevant composition classes (simply switch this marshaling behavior off), see you comment.
I have tens of view models deriving from PropertyChangedBase, Screen, Conductor etc, and I simply do not wish that all property changes will be marshaled to the UI thread.
IMHO it doesn't make sense to manually override OnUIThread per VM, and also not to create NotUIMarshaledPropertyChangedBase, NotUIMarshaledScreen etc.
WPF is handling property changes internally, please allow a way to disable this globally, since the default will be UI marshaling enabled, this won't be a breaking change.
"I would consider adding it to IPlatformProvider since it's static and then be configured solution wide." sounds like a perfect solution.
Thank you for everything.