简体   繁体   中英

Qt: Signal main thread

Lots of similar questions exists, but no suitable answer found.

I use a 3rd party library.

When some of the virtual methods on the lib's classes are called, these are called from a worker thread that has not been started by my application. This thread is not a QThread, nor could it ever be.

I can emit from this thread, but only if I connect the slot using Qt::DirectConnection. The upshot is that QObject::sender() in the SLOT will always return NULL. I wish to call deleteLater() for instance, but that can only be scheduled in a QThread.

I think I need to get back to the main thread, but how can I signal the object on the main thread?

Example: when the below method is called, it is done so on a thread created by 3rd party library.

/*virtual*/ bool MediaPlayer::onEof()
{
  stopTransmit();
  emit sigFinished();  // slots only called if bound using Qt::DirectConnection
  deleteLater();       // dtor is never called

  return false;
}

The connection is made from within a non-QThread context also, as follows:

/*virtual*/ void
SipCall::state_answer_call::onEntering(SipCall& ref)
{
  ...
  MediaPlayer* player = new MediaPlayer;
  ref.connect(player, SIGNAL(sigFinished()), SLOT(slotMediaFinished()), Qt::DirectConnection);
  ...
}

Without the explicit Qt::DirectConnection , SipCall::slotMediaFinished() is never invoked.

The problem is that your MediaPlayer instance player is created on a thread that doesn't have an active event loop.

When you change the code to...

ref.connect(player, SIGNAL(sigFinished()), SLOT(slotMediaFinished()), Qt::QueuedConnection);

the Qt infrastructure will post an event to the thread associated with player . As there is no event loop to process that event the target code MediaPlayer::slotMediaFinished will never be called.

So the question is... which thread do you want MediaPlayer::slotMediaFinished to be called on? If it's the main application thread you could try (assuming you're using qt5 and c++11)...

ref.connect(player, &MediaPlayer::sigFinished, QCoreApplication::instance(),
  [&]()
  {
    player->slotMediaFinished();
  },
  Qt::QueuedConnection);

That will use the QThread associated with the QCoreApplication instance as a context in which to execute the lambda.

Edit:

As you've stated you can't use qt5 or c++11 the only other option I can suggest is to have a QObject derived variable on your main application thread that can act as a proxy context for player ...

class proxy: public QObject {
  Q_OBJECT;
public slots:
  void slotMediaFinished ()
    {
      if (MediaPlayer *player = dynamic_cast<MediaPlayer *>(sender()) {
        player->slotMediaFinished();
      }
    }
};

Create an instance of the above on your application thread and then your connect statement would be...

ref.connect(player, SIGNAL(sigFinished()), &proxy_instance, SLOT(slotMediaFinished()), Qt::QueuedConnection)

Where proxy_instance the, er, proxy instance. Now, when the sigFinished signal is emitted Qt will post the event to proxy_instance . That in turn will call proxy::slotMediaFinished which can identify the MediaPlayer instance of interest from QObject::sender and invoke its slotMediaFinished member.

When some of the virtual methods on the lib's classes are called, these are called from a worker thread that has not been started by my application. This thread is not a QThread, nor could it ever be.

There's a mistaken belief that it's a problem. It is not. The thread where you emit the signals is immaterial. It doesn't have to be started using QThread .

In fact, emitting a signal from a C callback is an idiomatic way of interfacing multithreaded C callback APIs to Qt. It is meant to work without any effort.

I can emit from this thread, but only if I connect the slot using Qt::DirectConnection.

That's not true. When the signals and slots live in different threads, you can only use direct connections if the slots/functors you connect to are thread safe. I doubt yours are, so you should not use direct connection. The automatic connection will do exactly what you need.

It doesn't work because the receiving SipCall instance doesn't live in a thread with a running event loop. You'll have to either move it to such a thread, or use a thunk functor living in the main thread as suggested in the GM's answer to this question .

The problem with thunk functors is that they obfuscate thread ownership of the object you're invoking the methods on. Most likely the SipCall methods aren't thread-safe either , so by calling them from the main thread you're bound to break things. It's safest to move the player to the application thread, and ensure it's only used from that thread. You won't need thunk functors then.

If you want to see what ways there are of running some code (eg a functor) in a given thread, see this question .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM