Best way to call async method from processBlock() loop?


#1

I’ve got a heavy loading function that is occasionally triggered from the processBlock() loop.

I would like the function to be called asynchrously as right now it blocks the audio.

I’m looking at MessageManager’s

void* callFunctionOnMessageThread (MessageCallbackFunction* callback, void* userData);

…but I’m stumped!

How do I set up a normal member function say

void loadFile (int fileNumber);

…to work in this way?

Many thanks in advance!


Sending signal/events from audio to GUI thread?
#2

This is a bit of a classic problem (for real time audio). In general you can’t do anything interacting with the GUI subsystem (this includes async notifications, messages), memory allocation, locks or files etc. as it can potentially stall the audio thread.

The super-safe, works-out-of-the-box solution is to set a flag from the audio thread, and use a GUI timer to regularly check the flag and perform the function.

One would clasically use events or condition variables for this sort of task, however there’s no cross-platform guarantee of deterministic behaviour (so they may block indefinitely). All the library code / kernel implementation code I’ve read uses mutexes internally to solve this problem, so technically that’s a no go as well. Go with the timer if possible.


#3

Thanks for the info! Very helpful.

I guess the reason I was keeping it on the PluginProcessor side is that the GUI isn’t always open (sometimes is never opened).

I suppose I could run a timer on the PluginProcessor which checks as you’ve described


#4

That, or you could use an AsyncUpdater, which works like this:

you inherit from AsyncUpdated and then trigger an async update on whatever thread you like (the audio thread in your case) with triggerAsyncUpdate().

Soon after that, your virtual callback handleAsyncUpdate() will be invoked by on the message thread, and there you can do your time consuming activity, decoupled from the audio thread.


Please also note, for future readers of this topic, that

void* callFunctionOnMessageThread (MessageCallbackFunction* callback, void* userData);

would not work for this purpose because that call would still block the audio thread that makes it. Quoting the doc:

if another thread is calling, a message will be posted to the queue, and this method will block until that message is delivered, the function is called, and the result is returned.


#5

Well if you really want to avoid memory-allocation on the audio-callback, i wouldn’t do this.
The async-message itself is preallocated but it posts a message to the message Array (MessageQueue::messages) which will be dynamically realloced and uses a CriticalSection.

Another option could be to set an Atomic flag, and use another thread which is checking this flag and do the heavy work there.

Anyway, something which i think is really missing in JUCE, is a lockless allocator which can be used in the audio-thread or anywhere.

(Yes i know there are other techniques, like atomically swap pointers on the audio-thread, to avoid the creation of heavy weight object on the audio-thread, but if want to implement something like this, which ensures exact automation, this isn’t always possible, or adds a level to complexity to the code which should be avoided.) A lot of problems could be solved much more simpler if there were a lockless and FAST memory allocator.


#6

Oh sh*t… strange that I didn’t get any problem yet, I’m using this approach since years… May have to rethink a thing or two.

I guess the same issue affects ChangeBroadcaster / ChangeListener so that’s a no-go either, right?


#7

Oh sh*t… strange that I didn’t get any problem yet, I’m using this approach since years

Computers are fast today :wink:

I guess, yes. But maybe no one will ever notice.


#8

Thanks all for the interesting insights!

My take on the first approach (flag + timer) was this (it’s for a IR convolution plugin)…

class ImpulseLoaderAsync  : private Timer
{
public:
    ImpulseLoaderAsync();

    void changeImpulseAsync (int newImpulse);
    const bool isNowChanging() { return nowChangingImpulseAsync; }

private:
    int currentImpulse {-1};    // force initial load
    bool nowChangingImpulseAsync {false};      // the flag

    void timerCallback() override;
    void changeImpulse (int newImpulse);
};

.cpp…

#include "ImpulseLoaderAsync.h"

ImpulseLoaderAsync::ImpulseLoaderAsync()
{
    startTimer (500);
}

void ImpulseLoaderAsync::changeImpulseAsync (int newImpulse)
{
    if (newImpulse != currentImpulse)
    {
        currentImpulse = newImpulse;
        nowChangingImpulseAsync = true;
    }
}

// private:

void ImpulseLoaderAsync::timerCallback()
{
    if (nowChangingImpulseAsync)
    {
        changeImpulse (currentImpulse);
        nowChangingImpulseAsync = false;
    }
}

void ImpulseLoaderAsync::changeImpulse (int newImpulse)
{
    switch (newImpulse)
    {
        // big switch statement where I load the impulse
    }
}

Note, I bypassed the DSP (in my case convolution) whenever the changeImpulseAsync() flag is set.

In the processBlock()…

impulseLoaderAsync.changeImpulseAsync (newImpulse);

if (*bypassParam < 0.5f && impulseLoaderAsync.isNowChanging() != true) // bypass while changing impulse also
{ 
    //... the DSP

How does all this look to you guys best practice-wise?

Thanks!


[SOLVED] Gui host freeze when using the "system" command
#9

Another problem with AsyncUpdater is that there is no guarantee it’ll be called in “reasonable” time. If a lot of messaging is going on, multiple processBlock calls could happen before the async call actually happens. Maybe that’s not a problem in your case but I got into a lot of trouble because of that. In the past some hosts caused problems when using the MessageManager. Maybe no longer, but better safe than sorry. I also use flags/variables set in processBlock and a polling timer to push information to the GUI.


#10

You may want to load/process the impulse on a secondary thread, the timer callback is still called on the main thread - there’s no reason to stall that. This is where you could use an event (or semaphore if you’re implementing a queue, what happens if you’re changing impulses while an impulse is being changed)?

Why are you changing impulses in the processing block, btw?

[quote=“pflugshaupt, post:9, topic:20748, full:true”]
Another problem with AsyncUpdater is that there is no guarantee it’ll be called in “reasonable” time. If a lot of messaging is going on, multiple processBlock calls could happen before the async call actually happens. Maybe that’s not a problem in your case but I got into a lot of trouble because of that. In the past some hosts caused problems when using the MessageManager. Maybe no longer, but better safe than sorry. I also use flags/variables set in processBlock and a polling timer to push information to the GUI.
[/quote]Note that the async updating and timers both use the message manager under the hood: They post messages that are consumed/called on the main thread.


#11

It depends on what kind of timer is used. Once the timer code is there, you can switch from Timer to HighResolutionTimer if necessary which calls on its own thread.


#12

Thanks all again for interesting discussion!

I have all parameters on the processor side, so I’m trying to think of the case where someone might never open the GUI editor i.e. automation/host control.

But yes, it doesn’t make major sense for impulse changing, unless in the film sound world if people want to automate the impulse changing from scene to scene.

Perhaps I should be adding another message rate timing layer to the processor?

This way I could keep parameter handling away from the GUI but also not burden the audio rate processBlock() with less time-critical routines unnecessarily?


#13

Right ok, if the impulse is automatable it needs to be controlled in the audio thread if you want your plugin to be deterministic / sample-accurate.

However, the path you’re going down is very indeterministic (timers+worker threads)… I think, if you’re able to automate between different impulses, they should be preloaded. Don’t worry about memory usage, this is 2017. Loading arbitrary IRs should be something the user programs in the UI.


#14

Isn’t this unsafe because changeImpulseAsync() could be called again from the audio thread whilst the timerCallback() is executing, potentially corrupting the impulse loading operation? i.e. currentImpulse = newImpulse could be called whilst changeImpulse() is executing. To fix this I think you need to ignore concurrent change requests by only updating if nowChangingImpulseAsync is false, i.e.

if (newImpulse != currentImpulse && !nowChangingImpulseAsync) { ...


#15

Can someone clarify why is this locking the audio thread and not the tread being invoked? Wouldn’t that be the whole purpose of an AsyncUpdater? I’m confused, since the triggerAsyncUpdate() method is said to return immediately according to the docs.


#17

I was referring to MacOS/iOS, where the message is posted into this array
in the MessageQueue class:

 void post (MessageManager::MessageBase* const message)
    {
        messages.add (message);
        wakeUp();
    }

messages uses a CriticalSection and may be dynamically reallocated.

ReferenceCountedArray<MessageManager::MessageBase, CriticalSection> messages;

Maybe this could be replaced by a lock-free Fifo
Wonder what @jules opinion is?


Component repaint() continuous - problem
#18

There’d be little point making the message queue lock-free on OSX if it still uses locking on other platforms, because you wouldn’t be able to rely on lock-free behaviour in cross-platform code.

Also, even replacing the array with a fifo, it’d still need to call CFRunLoopSourceSignal and CFRunLoopWakeUp, and who knows what happens inside them!


#19

Thanks for clarification!

Indeed it would be extremely helpful, if the implementation for asynchronous notifications like ChangeBroadcaster::sendChangeMessage() and AsyncUpdate::triggerAsyncCallback() is designed to work safely in audio-callbacks, without priority inversion.

(Instead anybody is cooking there own solution, i know there are a lot of workarounds.)

Or some kind of easy design pattern (drop in replacement), to use these methods on a realtime thread.

I see this all around the juce code, where are atomic flags are used, and extra threads/timer to read this flag, it would be cool to have it just in the box

EDIT: parameter change callbacks are also often from the same audio-thread