开发者

Knowing when a QThread's event loop has started from another thread

开发者 https://www.devze.com 2023-02-16 14:11 出处:网络
in my program, I am subclassing QThread, and I implemented the virtual method run() like so: void ManagerThread::run() {

in my program, I am subclassing QThread, and I implemented the virtual method run() like so:

void ManagerThread::run() {

    // do a bunch of stuff,
    // create some objects that should be handled by this thread
    // connect a few signals/slots on the objects using QueuedConnection

    this->exec(); // start event loop
}

Now, in another thread (let's call it MainThread), I start the ManagerThread and wait for its started() signal, after which I proceed to use the signals and slots that should be handled by ManagerThread. However, the started() signal is essentially emmitted right before run() is called, so depending on thread scheduling I lose some signals from MainThread, because the event loop hasn't started yet!

(EDIT: turns out that's not the problem, it's just the signals are not connected in time, but for the same reason)

I could emit a signal right before calling exec(), but that's also asking for trouble.

Is there any definitive/simple way of knowing that the event loop has started?

Thanks!

EDIT2:(SOLUTION)

Alright, so it turns out the problem isn't exactly what I phrased. The fact that the event loop hasn't started isn't the problem, since signals should get queued up until it does start. The problem is, some of the signals would not get connected in time to be called- since the started() signal is emitted before run() is called.

The solution is to emit another custom signal after all the connections and right before exec. That way all signals/slots are ensured to be connected.

This is the solution to my problem, but not really an answer to the thread title. I have accepted the answer that does answer the title.

I have left all my code below for those curious, with the solution being, to wait for another signal in the instance() method.


CODE:

Many of you are saying that I cannot lose signals, so here is my whole class implementation. I will simplify it to just the bare necessities.

Here is the interface to ManagerThread:

// singleton class
class ManagerThread: public QThread {

    Q_OBJECT

    // trivial private constructor/destructor

    public:
    static ManagerThread* instance();

    // called from another thread
    public:
    void doSomething(QString const& text);

    // emitted by doSomething,
    // connected to JobHandler whose affinity is this thread.
    signals:
    void requestSomething(QString const& text);

    // reimplemented virtual functions of QThread
    public:
    void run();

    private:
    static QMutex s_creationMutex;
    static ManagerThread* s_instance;
    JobHandler* m_handler; // actually handles the requests
};

Some relevant implementations. Creating the singleton instance of the thread:

ManagerThread* ManagerThread::instance() {
    QMutexLocker locker(&s_creationMutex);
    if (!s_instance) {
        // start socket manager thread, and wait for it to finish starting
        s_instance = new ManagerThread();

        // SignalWaiter essentially does what is outlined here:
        // http://stackoverflow.com/questions/3052192/waiting-for-a-signal
        SignalWaiter waiter(s_instance, SIGNAL(started()));
        s_instance->start(QThread::LowPriority);
        qDebug() << "Waiting for ManagerThread to start";
        waiter.wait();
        qDebug() << "Finished waiting for ManagerThread thread to start.";
    }

    return s_instance;
}

Reimplementation of run that sets up signals/slots and starts event loop:

void ManagerThread::run() {
   // we are now in the ManagerThread thread, so create the handler
   m_handler = new JobHandler();

   // connect signals/slots
   QObject::connect(this,
                    SIGNAL(requestSomething(QString const&)),
                    m_handler,
                    SLOT(handleSomething(QString const&)),
                    Qt::QueuedConnection);

   qDebug() << "Starting Event Loop in ManagerThread";

   // SOLUTION: Emit signal here and wait for this one instead of started()

   this->exec(); // start event loop
}

Function that delegates the handling to the correct thread. This is where I emit the signal that is lost:

void ManagerThread::doSomething(QString const& text) {

    qDebug() << "ManagerThread attempting to do something";

    // if calling from another thread, have to emit signal
    if (QThread::currentThread() != this) {
        // I put this sleep here to demonstrate the problem
        // If it is removed there is a large chance the event loop
        // will not start up in time to handle the subsequent signal
        QThread::msleep(2000);  
        emit(requestSomething(text));
    } else {
       // just call directly if we are already in the correct thread
       m_handler->handleSomething(text);
    }
}

Finally, here is the code from MainThread that will fail if the event loop doesn't start in time:

ManagerThread::instance()->doSomething("BLAM!");

Assuming that the handler just prints out its text, here is what gets printed out on a successful run:

Waiting for ManagerThread to start

Finished waiting for ManagerThread thread to start.

Starting Event Loop in ManagerThread

ManagerThread attempting to do something

BLAM!

And here is what happens on an unsuccessful run:

Waiting for ManagerThread to start

Finished waiting for ManagerThread thread to start.

ManagerThread attempting to do something

Starting Event Loop in ManagerThread

Clearly the event loop started after the signal was emitted, and BLAM nev开发者_开发技巧er prints. There is a race condition here, that requires the knowledge of when the event loop starts, in order to fix it.

Maybe I'm missing something, and the problem is something different...

Thanks so much if you actually read all that! Phew!


If you setup the connections right, you shouldn't be losing the signals. But if you really want to get a notice on the start of the thread's event loop, you can try QTimer::singleShot() in your run() right before calling exec(). It will be delivered when the event loop starts and only delivered once.


You could look at QSemaphore to signal between threads. Slots and signals are better for ui events and callbacks on the same thread.

Edit: Alternately you could combine QMutex with QWaitCondition if a semaphore is not applicable. More example code to see how you are using the ManagerThread in conjunction with the MainThread would be helpful.


This is a non-issue. Signals between threads are queued (more specifically, you need to set them up to be queued in the connect() call because direct connections between threads aren't safe).

http://doc.qt.io/qt-5/threads-qobject.html#signals-and-slots-across-threads


You could create the signal/slots connections in the constructor of the ManagerThread. In that way, they are certainly connected even before run() is called.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号