2

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.

iwarv
  • 335
  • 4
  • 13
  • Hi, since your worker-thread comes from another API you have to use this API's methods. But anyhow you managed to signal from this implementation. Would it be a solution to hold a pointer to your thread in your app, let thread signal the end of your thread, connect (to your Qt MainThread) and delete in slot. (If you are already in Mainthread you don't have to wait till deleteLater). Or, if you have to wait, dont delete the object, but flag it and delete flagged objects whenever you want. – Bernhard Heinrich Aug 30 '16 at 10:16
  • I'm not sure I understand. It should be ok to emit a signal to a queued connection from a non-`QThread` -- so long as the target code *is* executed on a `QThread`. How are you creating the connection? Can you show the code? – G.M. Aug 30 '16 at 10:33
  • @G.M. The connection is your usual QObject::connect() variant. Nothing special about it. But your comment made me think that perhaps the connection itself occurs within another library thread. So I added debug and confirmed this: the connection is made from within another (not Qt) worker thread. – iwarv Aug 30 '16 at 11:21
  • There are at least five `QObject::connect` variants. So again, can you please show the code that creates the connection -- I think it needs to be one of the `QObject::connect` forms that allows you to specify the target context. – G.M. Aug 30 '16 at 11:42
  • Question has been edited as requested. – iwarv Aug 30 '16 at 12:14

2 Answers2

2

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.

G.M.
  • 12,232
  • 2
  • 15
  • 18
  • Alas, we're well behind on the curve here and still use Qt4.7. Because we need to X-compile to other platforms (hence Qt) we cannot use C++0x or C++11 extensions (yet). – iwarv Aug 30 '16 at 12:46
  • @iwarv That just means you have to type more boilerplate code. A lambda does nothing special. – D Drmmr Aug 30 '16 at 12:56
  • Would I not re-emit from that proxy? It is class `SipCall` that has method `slotMediaFinished()`, not class `MediaPlayer`. Admittedly, the proxy solution presumably solves the `deleteLater()` problem (as yet untested) – iwarv Aug 30 '16 at 13:13
  • Despite your efforts (which are most appreciated), I don't think the end justify the means. If heavens forbid I was run over by a bus tomorrow then some poor colleague would have to pick this up. I may need to simplify and look into a different approach. Damn this library and its overzealous threading! – iwarv Aug 30 '16 at 13:21
  • @iwarv Threads are supposed to be easy, there's nothing "overzealous" about threading as long as the interface you expose to the asynchronous process is sane. Perhaps you should clarify what's going on in your design. Is `SipCall` a class that you control? If it is, then why doesn't it already live in the main thread, or a thread with a running event loop. I write "zealously" multithreading code all the time, and expose sane `QObject` interfaces to it that completely abstract all of it out. You should too. – Kuba hasn't forgotten Monica Aug 30 '16 at 13:30
  • `SipCall` is a class derived from a class in the 3rd party library. Although my application instantiates the class, I hand over control to it to this library. From there on I cannot control from what thread(s) its virtual methods are called. I can *however* move this class to a different thread upon creation as you stated in your answer. That seems to have made some promising results. – iwarv Aug 30 '16 at 14:04
1

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 G.M.'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 (e.g. a functor) in a given thread, see this question.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Thanks @kuba-ober, but can you clarify; you inadvertently created a portmanteau between classes `SipCall` and `MediaPlayer`. Which is the class that has to be moved to the main thread? – iwarv Aug 30 '16 at 13:36
  • Refresh the page :) In all cases, you care about the receiving class. The sender class's `thread()` doesn't matter, because automatic connection always checks the sender thread at the time of signal emission, *not* at the time of establishing a connection. That's also why automatic connection is usually a good thing, and overriding the connection type must be reserved for special cases. Off the top of my head I can only think of two scenarios: when you want to invoke the slot from the event loop even if it's in the emitting thread, or when you are calling a thread-safe slot. – Kuba hasn't forgotten Monica Aug 30 '16 at 13:44