I have been dealing with multithreading issues for a while now. In the past few days I have been trying to ensure all my calls are thread safe. I have just run into an issue that has thrown me. Here is the scenario:
I am attempting to plot a waveform which is passing in ~500 points/sec/waveform and I am displaying 4 waveforms. Upon launch of the application I create 4 objects that have an ObservableCollection property and these properties are bound directly to the xaml in an itemscontrol. All the data comes in and is stored in a Queue and I spawn off a worker thread to pull the data from the queue and update the collection.
Spawn worker thread:QueueProcessThread = Task.Factory.StartNew(() => UpdateWaveFormCollections(WaveForms), tokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
Code to update the collection which runs in a loop (some lines of code omitted for brevity):
waveForm.LastDisplayedTimeStamp = DateTime.Now; // move the last displayed time up
int collectionSize = waveForm.WaveData.Count; while (waveForm.WaveDataBuffer.Count > 0 && waveForm.WaveDataBuffer.Peek().TimeStamp < waveForm.LastDisplayedTimeStamp) { if (waveForm.CurrentPosition >= collectionSize) { waveForm.CurrentPosition = 0; } waveForm.WaveData[waveForm.CurrentPosition] = waveForm.WaveDataBuffer.Dequeue(); waveForm.CurrentPosition++; }
As you can see, I do not actually add/remove items to the collection but instead just update the item at a specific position. This is because I wanted the look like a patient monitor at a hospital.
The problem I am running into is that I realized that I am updating this collection on a non UI thread and that collection is bound to the LineSeries directly. And this works. However, another graph using a StepLineSeries throws an exception when I update that collection on a non UI thread which is expected. How is it possible that I can update the bound collection on a non UI thread? I am concerned by this because 1) occasionally I do get an error that a collection cannot be updated on a non UI thread and 2) when I switched this call to update on the UI thread via a dispatcher the performance was so bad the GUI was unusable. I need to understand why this works so I know how to proceed. I do not want to deploy an application that might fail at any time due to thread mismanagement on my part. I am looking for possible reasons why I can do this but if needed I will try to create a sample. I am just on a very tight time limit to get this released.
Hi Mike,
I've asked our engineering team for comment on this. I'll let you know when they get back to me.
Regarding performance using a dispatcher, I'd expect this if you were running a dispatch per item. That would end up being alot of cross thread activity which can slow things down. A better approach might be to batch update your points. Let's say you'd update n number of points and store the final results in a temporary location not tied to the UI, then after n points have been updated, you push all the results for the batch to the UI thread using a dispatcher and updating the associated points. This would result in fewer dispatcher calls which should improve the performance.
If I batch the updates would I just replace the whole collection or are you suggesting I update, lets say, positions 300 - 350 of the collection in a batch? If you mean update those positions in a batch how would I replace all those positions at once? I will try replacing the entire collection after I batch up changes and see if performance is acceptable. Please let me know if you had a different approach in mind.
I meant that you would do the calculations as you normally do but don't actually modify the collection, just store the new positions for each point in another list and then after lets say 50 points you run a dispatcher on that list of results back to the UI and then update the collection. That's more of what I had in mind.
Our developers got back to me and it seems the chart may, in some circumstances, allow updating the bound data from a background thread but this is more because it is missing a check to report an error than an intended way to cause updates to occur. The idea you should stick with is you shouldn't update the ObservableCollection on a non UI thread that is bound directly to the chart.
Regarding the performance using Dispatcher, the developers have provided input on this as well.
"If you cause too many separate transitions to the UI thread, you can lose a lot of performance to the context switching overhead. The chart also batches its work based on separate threaded interactions. For example, if you change 30 properties the chart will defer any updates until after all 30 property changes are done because it waits for your threaded interaction to yield before updating itself. But if you are very chatty with the UI thread (making hundreds of updates a second and having every single one be a separate round trip to the UI thread) then you will create a performance issue where the chart needs to react and render to every separate change that you make.
To improve performance of marshaling a lot of data to the UI thread you should make your interactions with the UI thread more coarse grained. If you buffer and batch the updates into fewer interactions with the UI thread, this will improve performance."
So they are basically inline with what I said earlier about batch updating. This is the best way to handle this issue. They've also provided some input on batch updating.
"You do not necessarily need to marshal the data onto the UI thread at the same frequency it is generated. I would recommend, for example, throttling the amount of changes that can move to the UI thread to 30 times a second. Buffer these changes and send the changes that occurred over the 1/30 of the second since the last update, and send that all as one interaction with the UI thread. This way you will have a nice FPS refresh rate in the chart, but you are not allowing the background threads to flood the UI thread with context switching overhead."
Let me know if you have any further questions on this matter.