2

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 never 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!

Arnold Spence
  • 21,942
  • 7
  • 74
  • 67
Alexander Kondratskiy
  • 4,156
  • 2
  • 30
  • 51
  • 1
    Are you sure you are losing signals? If the signals are connected from within the thread, they they are set as queued signals. If they are connected from without a thread, they are synchronous signals. It'd be helpful to see the rest of the scenario to see how / when signals are connected. – lefticus Mar 10 '11 at 15:51
  • @lefticus I have updated my post with the code required to see the problem. At the end I have included the outputs of two runs of the program. One succeeds (event loop created in time), the other fails (signal lost before event loop is created). – Alexander Kondratskiy Mar 10 '11 at 18:26

4 Answers4

3

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.

Christophe Weis
  • 2,518
  • 4
  • 28
  • 32
Stephen Chu
  • 12,724
  • 1
  • 35
  • 46
  • It turns out my problem wasn't exactly what I asked, so I didn't need to know when the event loop started. However, since that is the title of the question, this would be the correct answer. In particular, `QTimer::singleShot()` could emit a signal, that should be waited for. When the thread enters the event loop, the `QTimer` is handled, the signal is triggered, and we know the event loop has started! Thanks! – Alexander Kondratskiy Mar 10 '11 at 19:13
1

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.

Christophe Weis
  • 2,518
  • 4
  • 28
  • 32
AJG85
  • 15,849
  • 13
  • 42
  • 50
  • 1
    I could try to `acquire()` on the semaphore in the `MainThread` (thus waiting), but the point is, what will call `release()` on the semaphore to tell me that `ManagerThread`'s event loop has started? – Alexander Kondratskiy Mar 10 '11 at 16:01
  • The `connect()` method is thread safe but otherwise objects derived from `QObject` are not you will probably need some kind of locking mechanism regardless even when using `Qt::QueuedConnection` in `connect()` ... why down vote? – AJG85 Mar 10 '11 at 16:19
  • Understanding data synchronization is needed for any parallel programming. However, there are any number of situations where you don't need locks on the data objects themselves (pipeline model). The signal/slot mechanism in QT provides exactly that across threads because it's a threadsafe queue. Cross-thread communication is also easy using them because again, it's a threadsafe queue. Saying not to use it is, quite frankly, wrong especially given that the entire framework is based around them. – Brian Roach Mar 10 '11 at 16:36
  • Ah fair enough I was rather vague. I didn't mean to imply not using them at all. – AJG85 Mar 10 '11 at 17:22
1

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

Christophe Weis
  • 2,518
  • 4
  • 28
  • 32
Brian Roach
  • 76,169
  • 12
  • 136
  • 161
  • Auto (the default) chooses queued connections if the connections occur across threads, as the document you point to shows. – lefticus Mar 10 '11 at 16:51
  • Thanks - it's been a while since I've worked in QT. Though I think I always explicitly set it for the simple reason that when I look at the code I know exactly what it's doing. – Brian Roach Mar 10 '11 at 17:06
  • @Brian Roach I've posted the code in question, and all evidence points to signals being lost before the event loop is started. Thanks for the interest. – Alexander Kondratskiy Mar 10 '11 at 18:16
  • I don't think this code does what you think it does. All of your objects are owned by the main QT thread (anything you create in run(), etc). Look at thread affinity in the docs (It's rather screwy in my opinion, but it is what it is). I'd have to look at an old project for specifics, but I ended up creating a subclass of QThread that took a "worker" object as an argument to the constructor and changed the affinity there. – Brian Roach Mar 10 '11 at 18:40
  • @Brian Roach Nevermind you are right this is a non-issue. The problem is that the signals/slots aren't connected before they're called in some instances. I need to emit a signal after they're connected, and wait for that. No need to know when the event loop starts. Thanks! – Alexander Kondratskiy Mar 10 '11 at 18:43
  • @Brian Roach About your last comment, the objects I create in `run()` have an affinity for the `ManagerThread` and therefore it's event loop will handle the signals/slots of those objects. Thread affinity dictates which thread will handle signals/slots, and I needed the `JobHandler` slots to run in the `ManagerThread`. – Alexander Kondratskiy Mar 10 '11 at 18:47
  • I suspect you were calling ManagerThread::doSomething directly from another thread rather than sending a signal? – Brian Roach Mar 10 '11 at 18:49
  • @Brian Roach Yes, but that was the whole point of the `doSomething()` as it emits the correct signal so the call is then handled by `JobHandler`. I know it sounds convoluted, and "why don't you just use the signal all the time" but there is more to it, as in my actual program, doSomething would wait for the handler to finish. In a way, it allows calls from other threads to be blocking, but executed specifically in ManagerThread. – Alexander Kondratskiy Mar 10 '11 at 19:02
0

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.

Kurt Pattyn
  • 2,758
  • 2
  • 30
  • 42
  • The problem is that I need `JobHandler` to be created within the `ManagerThread` thread (note the distinction between created in a method of the class vs. actual creation within a specific thread). Thus, I need to create the `JobHandler` instance inside the `run()` method, which will be running in the correct thread. I cannot make the connections in the constructor, as the `JobHandler` instance does not exist yet. – Alexander Kondratskiy Mar 10 '11 at 18:23
  • @Alexander Can't you use MoveToThread to move the JobHandler to the thread when it is started? – Kurt Pattyn Mar 10 '11 at 18:26
  • I guess the would be possible. The problem has been solved, thanks for the suggestion though. – Alexander Kondratskiy Mar 10 '11 at 20:59