Giving value& to component (e.g. Label.getTextValue().referTo(...) ), and multithreading


#1

Hi!

This is a question on multithreading specifically on how it is to be used in Juce, relating back to my previous post here (http://www.juce.com/forum/topic/gui-unresponsive-even-through-cpu-cores-are-not-too-occupied-likely-i-need).

I often use the feature in Juce, of creating a GUI component (e.g. a Label), with a reference to a value object (Label.getTextValue().referTo(...)), which it is to control.

Then in my model I only keep value objects, and when I programmatically set their value the component is updated, and likewise the component setting the value following user interaction, raises an event and I can react to these changes.

But, above, how is threading dealt with? Is it safe to just ignore threading?

I assume, when programmatically setting a new value to the value object, I need to lock before, and when the Label/Component sets the Value following user input, it needs to do so.

Do I need to also deal with locking/threading whenever I have valueChanged methods that listen to the object?

So for example, a user enters a string using the Label, and in my model, I need to react to that event, so I subscribe to valueChanged(...). When that method is called, is it in the GUI thread, or in my "main thread", assuming that was where the value object was instantiated? If it is in the GUI thread, what do I need to do in valueChanged(...) to react to the change asynchronously from the GUI thread?

Is there an example that I could look at? I saw nothing in the JuceDemo, and I've looked at the analogous example simpleDJ which uses VFlib, but I wanted to see how this is done using Juce alone, so that I better understand the issue before considering to use an additional library.

Searching the forum on this I've only found another question along the same lines, but no answers (http://www.juce.com/forum/topic/best-practice-parameter-changes-and-ui).

Thank you!


#2

Unfortunately, as far as I can tell, the valueChanged comes on either the message thread, or the thread that called it, depending on whether it was triggered by a deferred update or immediate. I haven't worked out why it's sometimes one or the other (unless it changed in the juce codebase). Regardless, Values aren't thread-safe, so I don't think you should be touching them from a thread - your thread could be interrupted part way through, and another thread could access them.

 

Bruce


#3

Thank you for your replies here and in my other thread, they've been very useful in helping me understand what I'll need to do to fix my program!

So, finally, is there no way in which I can carry on as I did, of using a data structure relying on Value objects, and have a main thread and a GUI thread both read and write to it?

I saw how PropertyPanels and PropertyComponents worked with Value objects and thought it looked convenient, so unfortunately(?) I've followed this model everywhere, also in my own custom components.

In my software the whole point is to interactiverly and continuously modify the data structure, which is also updated based on incoming messages, so there is no clear line to draw between GUI events and model events, they are finely interwoven...

I was hoping that I could resolve this, not by ceasing to use Value objects, nor by maintaining two copies of my data structure, since both those solutions would require what at the moment seem like unnecessarily daunting amounts of changes to my code. I was hoping that I could instead follow the path of least resistance, through using locking whenever a Value object is read or written. This is what I did in my Java prototype version of the software.

But reading the Juce code for Label etc, I don't see any locking going on, and since all I do is give the Component a reference to my Value, I see no way in which I could get it to lock correctly before setting the Value.

Is there a way around this, using locking or otherwise, that will not require stopping to use Value the way I do? Or should I just start ripping Value out to replace it with something along the lines of Param as used in theVinn's simpleDJ example?

Thanks again!


#4

 

I had this issue for a long time.  I also had built my code such that the Value objects were accessed everywhere .. (including in the audio thread).  When I realized they were not thread safe, it through me for a loop.

 

 

Since I had already subclassed the Value I dropped a lock in there to make them thread safe, but this slowed things down a bunch.

So ... I finally solved it by overriding SetValue() in such a way that if the value is changed from the MessageManager thread, it will lock and change the Value as usual, but if it is changed from another thread it will just change a float value instead, then start a timer so that the message thread can later call Value::setValue().

 

It's not the most elegant solution, undoubtedly, but seems stable.  Curious to see how you solve this ....


#5

Although it is not a perfect lock free solution, it seems that something along those lines will have to do for now, I'm expected to use this software in performance soon and am wary to start making too big changes to it, but subclassing Value sounds like the most minimal approach possible!

If you're so inclined, I'd be delighted to see your Value subclass, although of course I understand if you'd prefer not to put it on here!


#6

Thinking about how this issue arose for you and me, and how I managed to avoid it when I was making my prototype in Java, I'm thinking that towards novice users, perhaps protecting them from multithreading early on is not a good pedagocical solution, if in the future the accumulated issue will turn around and bite them.

I remember when writing the prototype in Java & Swing, it started crashing because my main thread and the GUI thread were accessing the same data, and I was forced to deal with the issue organically from early on.

Looking back, I'd say learning about threading issues from early on, when the codebase was still small, was less daunting than running into issues related to threading hen the program is nearly finished :)

So my suggestion: perhaps the Juce demo program is multi-threaded by default? Or perhaps that when you instantiate a MainWindow, this is by default in a new thread unless the setting is overridden?

Of course don't take this as passive-aggressive complaining towards Juce, rather it is a reflection on how the learning experience may be improved for new users :)