【问题标题】:How to block thread until a function returns in another thread?如何阻塞线程直到函数在另一个线程中返回?
【发布时间】:2017-09-28 08:39:29
【问题描述】:

如果我有一个主线程和一个单独的永久工作线程 QThread:

// Main thread where the event loop and QCoreApplication runs.
class NetworkController : public QObject {
public:
Q_OBJECT

    void connect_to_ap()
    {
        WifiAP *myAP = netMgr.get_best_ap();

        myAP->connect("my_psk_password");
    }

    // This is a class from a 3rd-party library, which contains, owns
    //  and manages the lifetime of "WifiAP" instances.
    NetworkManager netMgr;
};

// Separate thread where a state machine runs.
class StateMachineWorker : public QObject {
public:
Q_OBJECT

    void on_ready_to_connect_event()
    {
        // HERE: How to trigger NetworkController::connect_to_ap() and 
        // *block* until it returns.
    }

    NetworkController *pNetCtrlr;
}

当状态机类进入特定状态时,它应该连接到 AP(接入点)。 NetworkController 具有连接 AP 的功能。

我正在尝试找出一种状态机可以以线程安全的方式执行此操作的方法。问题是NetworkManager 总是在更新它的WifiAP 实例列表:它们经常被创建和销毁。 StateMachineWorker 直接调用pNetCtrlr->connect_to_ap() 不是线程安全的(因为NetworkController 线程中的NetworkManager 可以同时删除WifiAP 实例)。

所以我想要在StateMachineWorker::on_ready_to_connect_event() 中以某种方式向NetworkController 发出信号以在NetworkController 自己的线程中运行其connect_to_ap() 方法,并阻止StateMachineWorker线程直到connect_to_ap() 完成它的工作。我希望状态机被阻塞的原因是,如果我没有阻塞并让它进入事件循环,它可能会收到一些事件,使其转换到另一个状态之前connect_to_ap()已经完成执行;这绝对不能发生。

用于保护NetworkManager 中WifiAP 列表的互斥锁不起作用,因为它们需要插入到第3 方库中。

【问题讨论】:

  • 使用互斥锁?? ^^
  • cond_var 或 mutex 应该可以完成工作
  • @Robert 请参阅我在问题中的最后一句话。互斥锁需要位于我不想编辑的第 3 方库中。没有必要互斥锁netMgr,因为WifiAP 实例会在NetworkManager 内自动删除(通过信号/插槽等等)。
  • 在 StateMachineWorker 线程上使用 wait() 吗?
  • StateMachineWorker 可以向NetworkController 发送信号,通知它应该运行connect_to_ap,然后使用QEventLoop 阻止自己。您可以将来自NetworkController 的信号连接到通知其工作结束的信号到事件循环的quit 插槽,这将解除对StateMachineWorker 的阻塞并让它进一步工作。有关QEventLoop 用法的一些详细信息,请参阅我的this 回答。

标签: c++ multithreading qt c++11 thread-safety


【解决方案1】:

您可以将QMetaObject::invokeMethod 与参数Qt::BlockingQueuedConnection 一起使用。这种连接类型为您添加了所有的阻塞逻辑,您根本不需要更改第三方库。

一个一般的例子:

objects.h

#include <QObject>
#include <QThread>
#include <iostream>

class Object : public QObject {
  Q_OBJECT

public slots:
  void foo() {
    std::cout << "Hello";
    thread()->msleep(2000);
    std::cout << " world!" << std::endl;
  }
};

class Caller : public QObject {
  Q_OBJECT

public:
  void call(Object* o) {
    std::cout << "Calling..." << std::endl;
    metaObject()->invokeMethod(o, "foo", Qt::BlockingQueuedConnection);
    std::cout << "Finished!" << std::endl;
  }
};

ma​​in.cpp

#include <QCoreApplication>
#include "objects.h"

int main(int argc, char* argv[])
{
  QCoreApplication a(argc, argv);

  QThread t;
  Object o;
  o.moveToThread(&t);

  t.start();

  Caller().call(&o);

  return a.exec();
}

对应的是要调用的方法必须是一个槽。如果connect_to_ap 还不是一个插槽,您可以创建一个桥接对象来完成这项工作,如下所述。

请记住,此 bridge 对象必须与NetworkController(在您的情况下为主线程)位于同一线程中,因此该插槽在正确的事件循环中排队。您可以查看QObject::moveToThread 了解更多信息。

快速草稿类似于:

class NetworkControllerBridge : public QObject {
  Q_OBJECT

  NetworkController* nc;

public:
  NetworkControllerBridge(NetworkController* nc_) : nc(nc_) {}

public slots:
  void connect_to_ap() {
    nc->connect_to_ap();
  }
};

// ...

void on_ready_to_connect_event()
{
  NetworkControllerBridge bridge(pNetCtrlr);
  bridge.moveToThread(qApp->thread());
  metaObject()->invokeMethod(&bridge, "connect_to_ap", Qt::BlockingQueuedConnection);
}

更新

通过invokeMethod 调用方法的另一种方法是将其标记为Q_INVOKABLE。虽然您仍然需要 bridge,因为您无法修改库,但我提到这一点是为了回答的完整性。

【讨论】:

  • 谢谢您,先生!完美的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-07-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-30
  • 2013-04-19
相关资源
最近更新 更多