0

In many applications the 'UI thread' is special and framework UI updates must be dispatched from this thread.

However, in order to avoid locking the UI slow tasks are executed in background threads and the results propagated back to the UI thread to be displayed.

Different frameworks solve this in slightly different ways; for example:

  • In WPF the Application.Current.Dispatcher can be used to spawn a task that runs in the UI thread.

  • On android, you can use runOnUiThread to push an action onto the event queue for the UI thread.

  • In QT you bind an event handler using their signal/slot system to receive notifications from the background thread and update using the UI thread event handlers.

  • In iOS you use something like dispatch_async(dispatch_get_main_queue()... to trigger a task on the UI event queue.

All slightly different, but in general the pattern is clear: the background thread creates a Task which it then schedules to be executed on some TaskQueue which the UI thread periodically services in a synchronous manner.

However, I'm not convinced this is actually best practice.

Certainly for few, large background tasks it is reasonable.

...but when you have a large fan-out into sub-threads, and a one-to-one delegation of task-complete -> UI task, this simply smashes the UI with lots of small update tasks, defeating the purpose of moving work into backgrounds threads in the first place.

It seems much more beneficial to have an application level aggregation layer that combines and filters UI updates and only feeds the necessary ones through to the actual UI thread; in many ways this is effectively what the 'Virtual DOM' in react does (but without threads).

So, there's the use case.

Here's the question:

  • What's an effective pattern to write such a thread aggregation layer?

(...and notice that because this is a data-only operation, there is no requirement that this layer is served by a single thread like the UI thread event loop; it may in fact be optimal to service in-coming updates via a thread pool)

  • Your requirments are quite generics, why would you have many many threads that want to update the same current window? The user is viewing in a window the amount of data you can see is pretty limited so the need for updates them should also be limited. – Walfrat Sep 12 '17 at 7:30
  • A single dashboard could easily have multiple visualizations on it, all which may or may have multiple real time updating feeds. There's no reason a single view would would have 'limited' data to show unless you're building a trivial todo app. My question is actually quite specific: How do you orchestrate M threads into N tasks, where N < M, at an application level. – Doug Sep 12 '17 at 7:33
  • Yes but how much is M ? How much is N ? Have you performed benchmarks to see if your UI was really slowed down ? If so why is it slowed down ? COuld it be that when you refresh one data the whole dashboard is refreshed instead of only refresh the part it need to ? This would mean that if 3 thread update data of the same dashboard it get fully redrawn 3 times ? The only answer I see at the moment is to have component design in the way that they permit very localized refresh so it don't consume lot of UI Thread. – Walfrat Sep 12 '17 at 7:36
  • 1
    If your GUI becomes sloppy because of multiple, small updates, you meed to batch them into larger ones, preventing unnecessary refreshes from the OS. The number of background threads generating the data is pretty much irrelevant. Besides, i don't think there is a generic approach - it's a parallelism optimization problem and we have no way to guess what would work for you. – Tibo Sep 12 '17 at 9:15
  • 1
    ...but when you have a large fan-out into sub-threads, and a one-to-one delegation of task-complete -> UI task, this simply smashes the UI with lots of small update tasks, defeating the purpose of moving work into backgrounds threads in the first place. The purpose of these mechanisms is to make the UI more responsive by preventing blocking operations that will freeze user input, thus allowing the user to interact freely with the UI. Small updates don't generally have any impact on this freedom, unless you have an enormous amount of them. – Robert Harvey Sep 12 '17 at 14:59
1

In general, it's best to start writing your application naively since in most cases your UI will remain responsive and it's easier to maintain. However, I have run into situations where the UI gets bogged down with 1,000s of "tasks" in the Queue--most of which are duplicates. It happens often enough in near real-time applications where you are processing one or more streams of data and visualizing them on screen.

I worked on an application like that, and our choice was to keep the data stream separate from the UI. This required some preparation:

  • All data objects needed to either be immutable or not notify the UI that it changed
  • All updates were done on a clock

Essentially the background tasks were free to update the data, adding and removing elements, etc. Once a second the UI looked at the data and updated itself. Since the data was not thread-bound, this didn't cause any problems with synchronization, etc.

The update threshold really depends on your user expectations. For us, we had our sample periods be our limiting factor, so there wasn't any point in updating more often than once a second. Others may have more frequent updates with more localized feedback.

This was the simplest, and most predictable way we could find to keep data and UI in sync without all the micro-tasks that you can sometimes get when you use Dispatcher.BeginInvoke and similar constructs.

1

You're right, this is somewhat problematic, that's why it's already solved by the UI framework.

There is usually a way to post partial results to the UI thread. In Swing, that's done via SwingWorker, in Android - AsyncTask. When these are called on the UI thread, all previous partial results are collected and processed by the UI thread at the same time. If there is any way to save time/space by collating these partials instead of redoing the work - that's the time to do it. Easiest is to take the latest result and discard all previous ones (like in a progress bar), essentially meaning that the UI thread may stagger, but doesn't lag.

How this collection is handled - by checking for a previous partial when submitting a new one, or when the UI thread is ready to start processing is really not something you should tinker with unless you really know it's a problem. But of course you can have a look at the library code and find out what the various library designers thought was best.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.