暂停QThread的事件调度循环

时间:2014-07-27 17:58:26

标签: c++ multithreading qt

我有一个用Qt用C ++编写的多线程应用程序。目前,我的应用程序的工作原理是QThread实例(我没有子类QThread),它使用默认的run()实现,只调用QThread' s {{ 1}}方法,它提供事件调度循环。

我在某些exec()子类上调用moveToThread,这些子类实际上在一个单独的线程中执行我想要完成的工作。我告诉这些对象使用Qt的信号/插槽机制来完成工作。我通过通知我的worker对象停止工作,然后在我的线程上调用QObjectquit()来优雅地停止线程。一切都很好。

但是,我现在想要实现以下场景:

  • 用户点击,例如" X"我的应用程序上的按钮,因为他们想关闭它。
  • 我不想要启动任何新工作,所以我暂停了事件调度线程。如果调度的当前事件继续运行,那很好。
  • 我提示用户,允许他们a)丢弃所有剩余的工作并退出(使用wait() wait()` - 这已经有效),或者b)不要退出应用程序,但是而是继续工作(恢复线程)。

我的问题是quit() and似乎没有QThread方法。我在网上看到了各种添加一个的例子(比如this question的答案)。问题是这些示例依赖于自定义pause()实现,并在那里实现暂停。由于我依赖run()的事件调度循环,因此这些解决方案无法正常工作。我考虑过重新实现QThread或创建我自己的exec()子类,但这些解决方案似乎需要做很多工作才能获得简单的暂停/恢复功能。

什么是暂停QThread事件调度循环的最简单方法(阻止它调度任何新事件,但让当前事件继续)?

1 个答案:

答案 0 :(得分:1)

我尝试了评论中建议的方法,但是为了让它完全正常工作需要花点时间,所以这就是我最终的结果:

我将QThread子类化为添加两个新方法:pauseresume。有一些事情需要微妙处理:

  1. 线程仍在运行时调用start()不会执行任何操作。由于在线程的现有作业停止运行之前可能会调用resume(),因此我们需要在连接到线程finished()信号的插槽中执行实际的恢复。
  2. 可能在线程实际停止之前发出finished()信号。因此,我们需要在调用wait()之前在我们的广告位中致电start()
  3. 如果在线程已经停止后调用resume(),只需设置状态变量就不会起作用,因为永远不会发出finished()。因此,我们需要通过在resume()方法中使用非信号相关的恢复代码来处理这种情况。
  4. 这是最终产品。

    <强> pausablethread.h

    #ifndef INCLUDE_PAUSABLE_THREAD_H
    #define INCLUDE_PAUSABLE_THREAD_H
    
    #include <QThread>
    
    class QMutex;
    
    class PausableThread : public QThread
    {
        Q_OBJECT
    
        public:
            PausableThread(QObject *parent = 0);
            virtual ~PausableThread();
    
            void pause();
            void resume();
    
        private:
            QMutex *controlMutex;
            bool paused;
            bool resumeScheduled;
    
        private Q_SLOTS:
            void doResume();
    };
    
    #endif
    

    <强> pausablethread.cpp

    #include "pausablethread.h"
    
    #include <QMutex>
    #include <QMutexLocker>
    
    PausableThread::PausableThread(QObject *parent)
        : QThread(parent), paused(false), resumeScheduled(false)
    {
        controlMutex = new QMutex(QMutex::NonRecursive);
    
        QObject::connect(this, SIGNAL(finished()),
            this, SLOT(doResume()));
    }
    
    PausableThread::~PausableThread()
    {
        delete controlMutex;
    }
    
    void PausableThread::pause()
    {
        QMutexLocker locker(controlMutex);
    
        if(paused)
            return;
    
        paused = true;
    
        quit();
    }
    
    void PausableThread::resume()
    {
        QMutexLocker locker(controlMutex);
    
        if(!paused)
            return;
    
        if(resumeScheduled)
            return;
    
        if(isFinished())
        {
            start();
    
            paused = false;
            resumeScheduled = false;
        }
        else
        {
            resumeScheduled = true;
        }
    }
    
    void PausableThread::doResume()
    { /* SLOT */
    
        QMutexLocker locker(controlMutex);
    
        if(!resumeScheduled)
            return;
    
        paused = false;
        resumeScheduled = false;
    
        wait();
        start();
    }
    

    这似乎很有效。我相信如果线程碰巧完成或者在同一时间开始执行在另一个线程中的resume()doResume()内,则存在一些潜在的竞争条件。我不清楚如何解决这个问题。

    我尝试过使用以下内容覆盖超类的start()广告位:

    void start(Priority priority)
    {
        QMutexLocker locker(controlMutex);
    
        QThread::start(priority);
    }
    

    这个问题是start()在线程完成之前从未实际返回,因此它永远不会释放对互斥锁的锁定。因此,当doResume()尝试获取锁时,会遇到死锁,并且线程未成功恢复。

    我认为真正需要的是一个互斥锁,它在线程的运行状态发生变化时被独占锁定,但我不清楚如何实现它。

    无论如何,这场比赛发生的窗口非常小,而且这种情况很有效,并且#34;回答我的问题。感谢@ Lol4t0的建议!