One-shot threads (lambdas on their own thread that auto-delete)


#1

I’m experimenting with setting up one-shot threads (threads that only execute something once then quit) and am having trouble getting it to work. the threads run correctly, but i’m not sure how to shut them down after they complete. The idea was that I could run lambdas on their own threads (great for client-server messaging) without needing to write classes for each type of thread. Perhaps this is a noob approach but i’m curious how to do it properly.

class Receiver {
public:
    Receiver() {}
    ~Receiver() {}
    void callback(String result) { DBG( "Receiver::callback() result: \n" + result ); }
};

class AutoDeleteThread : public Thread {
public:
    AutoDeleteThread(std::function<bool()> task ) :Thread("AutoDeleteThread") {
        m_task = task;
        startThread();
    }
    ~AutoDeleteThread() {
        DBG("Shutting down AutoDeleteThread");
        stopThread(500);
    }
    void run() override {
        if( m_task() ) { DBG("OK"); } 
        else { DBG("failed"); }
        delete this; //throws JUCE assertion, because you can't call stopThread() on yourself
    }
private:
    std::function<bool()> m_task;
};

//usage:

    Receiver r;
    AutoDeleteThread* adt
        = new AutoDeleteThread( [&r]()
    {
        int statusCode = 0;
        URL serverRequestURL = URL("http://chordieapp.dev/AuthV2").withPOSTData("cmd=initialConnect");
        juce::ScopedPointer<juce::InputStream> stream(serverRequestURL.createInputStream(true, //bool usePostCommand
                                                                                         nullptr,    //progressCallback
                                                                                         nullptr,    //progressCallbackContext
                                                                                         juce::String(),   //headers
                                                                                         10000,  //timeoutMS
                                                                                         nullptr,    //respondHeaders
                                                                                         &statusCode)//statusCode
                                                      );
        if (stream != nullptr) {
            auto reply = stream->readEntireStreamAsString();
            r.callback(reply);
            return true;
        }
        return false;
    }
                                                 );
    DBG( "done with autoDeleteTest" );

#2

edit:

it seems that it works (deletes itself) if I do the following for the AutoDeleteThread:

~AutoDeleteThread() {
        DBG("Shutting down AutoDeleteThread");
        delete this;
    }
    void run() override {
        if( m_task() ) {
            DBG("OK");
        } else {
            DBG("failed");
        }
        //delete this; //throws JUCE assertion, because you can't call stopThread() on yourself
        signalThreadShouldExit();
    }

#3

You probably need to have a ChangeListener in the class which creates AutoDeleteThread then have AutoDeleteThread sendMessage() when it’s done and in the changeListenerCallback() delete the thread…

Rail


#4

when I stepped thru the code in the xcode debugger, the AutoDeleteThread was deleted without doing that. I’m wondering about using that ‘new’ operator like that, and if there’s a way to do it without it. The whole idea is to not have to even worry about using threads as class members if they’re only executing one time, but I need it to not execute on the Message Thread.

edit. spoke too soon. leaks when I shut down the test. The thread stops running, but it isn’t deleted. hmm…


#5

This works with a quick test:

Header:

class AutoDeleteThread;

 class AutoDeleteThreadActual : public Thread,
                                public ChangeBroadcaster
{
 public:

    AutoDeleteThreadActual (AutoDeleteThread* pParent);
    ~AutoDeleteThreadActual();

    void run();

 private:

    AutoDeleteThread*   m_pParent;

    bool                m_bRunning;

    void doSomething() noexcept;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoDeleteThreadActual)
};

 class AutoDeleteThread : public ChangeListener
{
 public:

    AutoDeleteThread()
    {
        m_pAutoDeleteThread = new AutoDeleteThreadActual (this);
        m_pAutoDeleteThread->startThread();
    }

    void changeListenerCallback (ChangeBroadcaster* source) override
    {
        if (source == m_pAutoDeleteThread)
            delete this;
    }

 private:

    ScopedPointer<AutoDeleteThreadActual> m_pAutoDeleteThread;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoDeleteThread)
};

Implementation:

 AutoDeleteThreadActual::AutoDeleteThreadActual (AutoDeleteThread* pParent) : Thread     ("Auto delete thread"),
                                                                              m_pParent  (pParent),
                                                                              m_bRunning (false)
{
    addChangeListener (m_pParent);
}

 AutoDeleteThreadActual::~AutoDeleteThreadActual()
{
    stopThread (2000);
}

 void AutoDeleteThreadActual::run()
{
    while (! threadShouldExit())
        {
        if (! m_bRunning)
            doSomething();
        }

    sendChangeMessage();
}

 void AutoDeleteThreadActual::doSomething() noexcept
{
    m_bRunning = true;

    wait (1000);

    signalThreadShouldExit();
}

In the calling component constructor as a test I did

 new AutoDeleteThread();

and I ran the app in debug without any errors.

EDIT: I’ve change the code to self delete.

Rail


#6

ok, this works without leaking

class AutoDeleteThread;
class Receiver {
public:
    Receiver() {}
    ~Receiver() {}
    void callback(String result);
private:
};

class AutoDeleteThread : public Thread {
public:
    AutoDeleteThread(std::function<bool()> task ) :Thread("AutoDeleteThread") {
        m_task = task;
        startThread();
    }
    AutoDeleteThread() : Thread("AutoDeleteThread") {}
    ~AutoDeleteThread() {
        DBG("Shutting down AutoDeleteThread");
        //delete this;
    }
    void run() override {
        if( m_task() ) {
            DBG("AutoDeleteThread::run() result: OK");
        } else {
            DBG("AutoDeleteThread::run() result: failed");
        }
        //delete this; //throws JUCE assertion, because you can't call stopThread() on yourself
        signalThreadShouldExit();
    }
    void changeTask( std::function<bool()> newTask) {
        m_task = newTask;
        if( !isThreadRunning() ) { startThread(); }
    }
private:
    std::function<bool()> m_task;
};

usage: Modify the default juce GUI app as follows:

class MainContentComponent   : public Component, public Timer
{
public:
MainContentComponent() { startTimer(1000); setSize(600,400); }
~MainContentComponent();

void paint (Graphics&) override;
void resized() override;

void timerCallback() override {
Receiver r;
adt.changeTask( [&r]()
                       {
                           int statusCode = 0;
                           URL serverRequestURL = URL("https://www.google.com");
                           juce::ScopedPointer<juce::InputStream> stream(serverRequestURL.createInputStream(true,                               nullptr, nullptr,juce::String(),10000,nullptr,&statusCode));
                           if (stream != nullptr) {
                               auto reply = stream->readEntireStreamAsString();
                               r.callback(reply);
                               return true;
                           }
                           return false;
                       }
                       );
DBG( "done with autoDeleteTest" );
stopTimer();

}
private:
AutoDeleteThread adt;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};

#7

I’ve updated my code above also to self delete and it also works without error

Rail


#8

But you can’t run a lambda in your code. that’s where ours differ. I should change the name of my class to be “OneShotThread”


#9

Ok, I post my ReferenceCountObject solution as well:

class OneShotThread : public Thread, public ReferenceCountedObject {
    // identical except run:
    void run() override
    {
        OneShotThread::Ptr holdMe (this);
        while (! threadShouldExit())
        {
            doSomething();
        }
        sendChangeMessage();
    }

    // and the typedef for the ReferenceCountedObject:
    typedef ReferenceCountedObjectPtr<OneShotThread> Ptr;

You have two references, one where you actually create it and the other one inside the execution you want to finish. Only when both references are gone, the object will self destruct. So you are on the safe side, no matter how long the thread runs (could be very short as well?)

No manual delete, no timer or anything needed…

EDIT: sorry, works for matkatmusic’s version as well:

void run() override {
    OneShotThread::Ptr holdMe (this);
    if( m_task() ) {
        DBG("OK");
    } else {
        DBG("failed");
    }
}

And no point in calling “signalThreadShouldExit();” if you don’t use the while loop, because all it does is to set the threadShouldExit() semaphore to allow a clean exit of the threads loop.
But you don’t have to loop, you just cannot interrupt then, only killing…


#10

I’m not a guru on lambdas… but if you need to pass a task pointer this also works:

class AutoDeleteThread;

 class AutoDeleteThreadActual : public Thread,
                                public ChangeBroadcaster
{
 public:

    AutoDeleteThreadActual (AutoDeleteThread* pParent, std::function<bool()> task);
    ~AutoDeleteThreadActual();

    void run();

 private:

    AutoDeleteThread*       m_pParent;

    std::function<bool()>   m_Task;

     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoDeleteThreadActual)
};

 class AutoDeleteThread : public ChangeListener
{
 public:

    AutoDeleteThread (std::function<bool()> task)
    {
        m_pAutoDeleteThread = new AutoDeleteThreadActual (this, task);
        m_pAutoDeleteThread->startThread();
    }

    void changeListenerCallback (ChangeBroadcaster* source) override
    {
        if (source == m_pAutoDeleteThread)
            delete this;
    }

 private:

    ScopedPointer<AutoDeleteThreadActual> m_pAutoDeleteThread;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoDeleteThread)
};

Implementation:

 AutoDeleteThreadActual::AutoDeleteThreadActual (AutoDeleteThread* pParent, std::function<bool()> task) : Thread     ("Auto delete thread"),
                                                                                                          m_pParent  (pParent),
                                                                                                          m_Task     (task)
{
    addChangeListener (m_pParent);
}

 AutoDeleteThreadActual::~AutoDeleteThreadActual()
{
    stopThread (2000);
}

 void AutoDeleteThreadActual::run()
{
    // Edited to removed some unnecessary boilerplate code

    m_Task();

    sendChangeMessage();
}

Cheers,

Rail


#11

Rail, can you explain your need for doSomething()?
In your code, doSomething() would never be called, because no outside objects change m_bRunning from false to true;


#12

it doesn’t have to exist – I just grabbed an existing Thread code which calls a class method… the main thing is you only want to call the task once.

Rail


#13

right, which is why I want to avoid the whole "while( !threadShouldExit() )" line altogether.

So, i’m wondering if there is a way shut down a thread as soon as whatever is in the run() method finishes executing.


#14

Well you can just call the task from run() but how will you handle if the thread is asked to quit externally? My code only runs the task once then stops the thread. In a perfect world the task should have a callback to quit prematurely too.

Cheers,

Rail


#15

if you look at my original post, you’ll see that i’m talking to a server with a 10-second timeout as the initial task I’m trying to queue up and run once. AFAIK, there’s no way to shut down URL::createInputStream() once you start it running.

I’m all for making this a generic tool anyone can use, but in my particular use case, I just need it to fire off these server connections one time and if they go thru, pass it to a receiver. if they don’t, then report ‘false’


#16

Well this will call the task and quit…

void AutoDeleteThreadActual::run()
{
    m_Task();

    sendChangeMessage();
}

And you can add Daniel’s suggestion as well.

Cheers,

Rail


#17

I’ll leave this on my DropBox

OneShotThread Code example

Rail


#18

Thanks, @Rail_Jon_Rogut and @daniel

Super helpful and insightful solutions!


#19

Good call on asking for this - it’s something I’d been meaning to add to the Thread class, and is really easy to do, I’ll have a shot at it soon…


#20

I’m curious what your solution will be, but @Rail_Jon_Rogut pretty much nailed it.