Questions regarding callAsync, callAfterDelay

Hi everybody,

I’m using MessageManager::callAsync for incoming midi messages to trigger functions on the message thread.

But now I want some of these functions to be triggered after a delay. Is it enough in this case to just use Timer::callAfterDelay and not use MessageManager::callAsync at all? It seems to work just fine but I’m not sure if it’s correct.

In other words, are

MessageManager::callAsync ([this] () { functionOnMessageThread(); });

and

Timer::callAfterDelay (0, [this] () { functionOnMessageThread(); });

equivalent ways of calling the message thread asynchronously?

Can both be called from any thread?

Timer::callAfterDelay clamps the delay at one milli second. So its not entirely the same, but you shouldn’t expect it to be accurate on more than 5ms anyway, so it should be good enough.

1 Like

The implementation of Timer::callAfterDelay is super easy to understand.
A one-shot lambda wrapper inheriting from juce::Timer.
And if you ask yourself about delete this :point_right:< Standard C++ >

IMHO except for sporadic usage, i would prefer to use a custom one, to avoid the ctor/dtor at each incoming midi message for instance.
But that’s just a vague premature-optimized opinion…

2 Likes

Oh yes, I was indeed asking myself about the delete this! Very interesting.
Thanks!

1 Like

But please aware of the usage with both of them. Care should be taken to make sure that the captured members in the lambda does not get deleted when the lambda gets executed.

When I use it in a juce::Component context, I do like,

// An extension to juce's callAfterDelay which also checks if the component is being present before calling the function
struct safeCallAfterDelay : private Timer
{
    safeCallAfterDelay(int milliseconds, std::function<void()> f, Component* component) 
         : function(std::move(f))
         , checker(component)
    {
        startTimer(milliseconds);
    }

    void timerCallback() override
    {
        if (checker.shouldBailOut()) //Checks if the GUI object has been deleted
            delete this;
        else
        {
            auto f = function;
            delete this;
            f();
        }
    }

    std::function<void()> function;
    juce::Component::BailOutChecker checker;
};

When in need, I do something like, new safeCallAfterDelay(3000, [&]{compAssociated.doSomething(); }, &compAssociated)

When using callAsync I captured a const reference sent to the function which was calling callAsync and experienced crashes which was then solved after I captured it by value.

1 Like

Thanks, that’s useful to know!

You can use a juce::Component::SafePointer in the lambda in that case.

For instance (picked from my code somewhere & recently) :

auto f = [p = juce::Component::SafePointer<BaseWindow> (this)]()
{
    if (p.getComponent()) { p->setVisible (true); p->addToDesktop(); p->toFront (true); }
};
    
#if JUCE_LINUX
    Timer::callAfterDelay (100, f);
#else
    f();
#endif
4 Likes

This looks neater than what I suggested.