【问题标题】:How to properly embed QEventLoop in the std::thread?如何在 std::thread 中正确嵌入 QEventLoop?
【发布时间】:2018-05-06 01:48:00
【问题描述】:

有一个对象包含std::thread,我想在对象被销毁时完成。

最少的工作代码:

#include <thread>
#include <future>
#include <QEventLoop>

struct Connector
{
    Connector(std::string addr, std::function<void(std::string)> cb)
    {
        std::promise<void> barrier;
        auto fut = barrier.get_future();

        m_worker = std::thread([addr, cb, &barrier]()
        {
            QEventLoop loop;
            QTimer::singleShot(0, [this, &barrier, &loop]
            {
                m_quit = [&loop] { QTimer::singleShot(0, &loop, &QEventLoop::quit); };
                barrier.set_value();
            });

            MySocket s(addr, cb);

            loop.exec();
        });

        fut.wait();
    }

    ~Connector()
    {
        m_quit();
        m_worker.join();
    }

    std::thread worker;
    std::function<void()> m_quit;
};

它变得非常快:你可以在loop上调用exit(),只有在它进入exec()之后,你不能在线程之外创建loop

我只有一个带有信号量的解决方案,该信号量由在此循环中排队等待执行的处理程序释放。当信号量被释放时,我可以确定循环已创建并正在运行,因此可以在需要时使用quit() 消息终止它。

我错过了更简单的方法吗?

【问题讨论】:

  • 你如何处理loop?您无法访问它,也无法向其发送事件。
  • @nwp,通过在 exec() 之前创建一些对象。类似于套接字的东西,传递一个 IP 地址来连接,std::function 回调数据。
  • 我没有看到您如何通过套接字与事件循环进行通信。看起来有点像您正在重新实现QThread。它已经带有事件循环和信号/插槽支持。
  • @nwp,我不需要QThread。问题是关于std::thread。在问题中添加了一个使用示例。
  • 我广泛使用std::threadQEventLoop。从那句话我可以想象MySocket 实际上是QTcpSocket 并且有一些未显示的插槽连接。顺便说一句,使用QThread,您完全可以在线程外创建loop,然后只需使用moveToThread

标签: c++ multithreading qt concurrency


【解决方案1】:

也许您可以将 QEventLoop 的 unique_ptr 的引用传递给线程,并在该指针上的销毁调用退出时。像这样:

#include <thread>
#include <QEventLoop>

struct Connector
{
    Connector()
    {
        m_worker = std::thread([=]()
        {
            event_loop = std::make_unique<QEventLoop>();
            loop->exec();
        });
    }

    ~Connector()
    {
        event_loop->exit();
        m_worker.join();
    }

    std::unique_ptr<QEventLoop> event_loop;
    std::thread worker;
};

【讨论】:

  • ~Connector() 可以在event_loop = std::make_unique&lt;QEventLoop&gt;(); 之前执行。在这种情况下,它会在 NULL 指针上崩溃。
  • 是的。您可以在构造函数中添加一个std::atomic_flag,并等待它在 make_unique 调用之后由线程设置。然后在析构函数中你可以调用event_loop-&gt;isRunning()来检查它是否可以退出。
  • 那么~Connector()中的event_loop-&gt;isRunning()可以在event_loop = std::make_unique&lt;QEventLoop&gt;();std::atomic_flag之后但在调用loop-&gt;exec()之前执行。它会调用event_loop-&gt;exit();而没有任何效果(或者如果你检查isRunning(),你甚至可能不会调用它),然后线程将挂在loop-&gt;exec();,而主线程将锁定在m_worker.join();
【解决方案2】:

这是我的看法,基于 katrasnikj 的回答和 std::promise docs,以确保在构造函数完成时线程正在运行。

struct Connector
{
    Connector()
    {
        std::promise<void> barrier;
        auto fut = barrier.get_future();
        worker = std::thread([=](std::promise<void>&& barrier)
        {
            event_loop = std::make_unique<QEventLoop>();
            barrier.set_value();
            event_loop->exec();
        }, std::move(barrier));
        fut.wait();
    }

    ~Connector()
    {
        QTimer::singleShot(0, event_loop.get(), &QEventLoop::quit);
        worker.join();
    }

    std::unique_ptr<QEventLoop> event_loop;
    std::thread worker;
};

但您可能想考虑改用QThread,因为它能够运行自己的事件循环。

【讨论】:

  • barrier.set_value(); 永远不会被执行。 event_loop-&gt;exec(); 在此之前阻塞。但是很高兴您提到了std::promise。我将在我的 QSemaphore 解决方案中使用它而不是信号量。我已经更新了问题。
  • 调用QTimer::singleShot()有问题:错误Timers can only be used with threads started with QThread。所以不能真正从非 EventLoop 主线程启动计时器。
  • 在这种情况下,您应该可以改用invokeMethod。我会在 1-2 小时内准备一个示例。
  • 是的,我不明白为什么它不起作用,所以这也是一种选择。
猜你喜欢
  • 2021-11-11
  • 2015-06-09
  • 1970-01-01
  • 2016-01-23
  • 2023-03-03
  • 1970-01-01
  • 2017-03-25
  • 2021-11-28
  • 1970-01-01
相关资源
最近更新 更多