Qt:信号主线程

时间:2016-08-30 09:44:42

标签: c++ multithreading qt

存在许多类似的问题,但没有找到合适的答案。

我使用第三方库。

当调用lib类中的某些虚拟方法时,会从我的应用程序启动的工作线程调用这些方法。这个帖子不是QThread,也不是QThread。

我可以从这个线程发出,但只有当我使用Qt :: DirectConnection连接插槽时。 结果是SLOT中的QObject :: sender()将始终返回NULL。 我希望调用deleteLater(),但是只能在QThread中调度。

我想我需要回到主线程,但是如何在主线程上发出信号呢?

示例:当调用以下方法时,它是在第三方库创建的线程上完成的。

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

  return false;
}

连接也是在非QThread上下文中进行的,如下所示:

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

如果没有明确的Qt::DirectConnection,则永远不会调用SipCall::slotMediaFinished()

2 个答案:

答案 0 :(得分:2)

问题是您的MediaPlayer实例player是在没有活动事件循环的线程上创建的。

将代码更改为...

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

Qt基础架构会将事件发布到与player关联的线程。由于没有事件循环来处理该事件,因此永远不会调用目标代码MediaPlayer::slotMediaFinished

所以问题是......你希望调用哪个线程MediaPlayer::slotMediaFinished?如果它是您可以尝试的主应用程序线程(假设您使用的是qt5和c ++ 11)...

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

这将使用与QThread实例关联的QCoreApplication作为执行lambda的context

修改

正如你所说,你不能使用qt5或c ++ 11我唯一可以建议的另一个选择是在你的主应用程序线程上有一个QObject派生变量,它可以充当代理上下文player ...

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

在您的应用程序线程上创建上述实例,然后您的connect语句将是......

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

其中proxy_instance,呃,proxy实例。现在,当发出sigFinished信号时,Qt会将事件发布到proxy_instance。反过来,我会调用proxy::slotMediaFinished来识别MediaPlayer感兴趣的QObject::sender实例并调用其slotMediaFinished成员。

答案 1 :(得分:1)

  

当调用lib类中的某些虚拟方法时,这些方法是从我的应用程序尚未启动的工作线程调用的。这个帖子不是QThread,也不是QThread。

错误地认为这是一个问题。它不是。发出信号的线程并不重要。它不必使用QThread开始。

事实上,从C回调发出信号是将多线程C回调API连接到Qt的惯用方法。它意味着不需要任何努力。

  

我可以从这个线程发出,但只有当我使用Qt :: DirectConnection连接插槽时才会发出。

那不是真的。当信号和插槽位于不同的线程中时,如果连接的插槽/仿函数是线程安全的,则只能使用直接连接。我怀疑你的是,所以你应该使用直接连接。自动连接将完全满足您的需求。

它不起作用,因为接收SipCall实例并不存在于具有正在运行的事件循环的线程中。您必须将其移动到这样的线程,或者使用生成在主线程中的thunk仿函数,如the G.M.'s answer to this question中所建议的那样。

thunk仿函数的问题在于它们会混淆您调用方法的对象的线程所有权。最有可能SipCall方法不是线程安全的 ,所以通过从主线程中调用它们,你必然会破坏它们。将播放器移动到应用程序线程是最安全的,并确保它仅在该线程中使用。那时你不需要thunk functors。

如果您想了解在给定线程中运行某些代码(例如仿函数)的方式,请参阅this question