Пауза и возобновление QThread

Недавно я начал изучать QThreads, и у меня есть программа, которая выполняет 4-часовой цикл в отдельном потоке (чтобы я мог продолжать использовать графический интерфейс). Что мне нужно, так это то, что будет приостанавливать/приостанавливать поток, когда пользователь нажимает кнопку pause qpushbutton, и когда пользователь нажимает кнопку возобновления qpushbutton, программа должна возобновиться. Как я могу этого добиться?

Я думал о посылке сигналов из моего основного класса; однако я не уверен, как я могу справиться с ними в потоке. Можно ли обрабатывать сигналы, отправленные из основного класса в потоке? В настоящее время у меня есть поток, излучающий сигналы в основной класс, и это работает нормально, но я не уверен, как отправлять потоки из основного класса и получать их в потоке.


person Jean-Luc    schedule 31.01.2012    source источник
comment
Ваш поток работает с циклом событий Qt или у него есть собственный рабочий цикл?   -  person Kamil Klimek    schedule 31.01.2012


Ответы (2)


Хорошо, поэтому я предлагаю вам создать внутреннюю переменную потока, которая будет проверяться на каждом этапе вашего цикла + QWaitCondition, чтобы возобновить его.

  1. Создайте метод паузы, в котором вы включите «поле паузы» (bool?), не забудьте его синхронизировать
  2. В вашем собственном цикле используйте QWaitCondition (см. документы Qt), чтобы приостановить выполнение потока.
  3. Создайте метод возобновления, в котором вы отключите «поле паузы» и разбудите QWaitCondition.

    class MyWorker: public QThread
    {
    private:
        QMutex sync;
        QWaitCondition pauseCond;
        bool pause;
    
    public:
        MyWorker(...): pause(false) {}
    
        void resume()
        {
            sync.lock();
            pause = false;
            sync.unlock();
            pauseCond.wakeAll();
        }
    
        void pause()
        {
            sync.lock();
            pause = true;
            sync.unlock();
        }
    
    protected:
        void run()
        {
            while(someCondition) // gues it's your loop
            {
                 sync.lock();
                 if(pause)
                     pauseCond.wait(&sync); // in this place, your thread will stop to execute until someone calls resume
                 sync.unlock();
    
                 // do your operation
            }
        }
    };
    
person Kamil Klimek    schedule 31.01.2012
comment
Спасибо за предложение. Я обязательно рассмотрю QWaitConditon. Будете ли вы предлагать мне возобновить обсуждение при нажатии кнопки? Потому что на данном этапе я не уверен, как изменить логическое значение в другом потоке на мой графический интерфейс. Еще раз спасибо. - person Jean-Luc; 31.01.2012
comment
Используйте QMutex, чтобы заблокировать участника в потоке, изменить его значение и разблокировать мьютекс. В вашем рабочем мьютексе блокировки потока (тот же мьютекс, что и при изменении значения), проверьте значение, если оно истинно, подождите этот мьютекс с вашим QWaitCondition. То же самое относится и к методу возобновления. Заблокировать мьютекс, изменить значение, разблокировать мьютекс, разбудить QWaitCondition. Ваш другой поток будет продолжать работать - person Kamil Klimek; 31.01.2012
comment
Фантастика! После вашего примера и чтения DOC я думаю, что понял. Наконец, я упомяну, что я читал, что наследовать QThread было «плохо», поэтому я наследую QObject и использую moveToThread, чтобы поместить работу в новый поток. Тем не менее, я уверен, что то, что вы предложили, будет работать нормально! - person Jean-Luc; 01.02.2012
comment
Весь QThread - вы делаете это неправильно - это понимание сходства потоков QObject и того, как сигналы доставляются в QObjects. Неплохо наследовать QThread, если вы знаете, что делаете, и понимаете, как все работает в Qt. - person Kamil Klimek; 01.02.2012
comment
Хорошо. Я не совсем профессионал в Qt, поэтому комментировать не буду. Я уверен, что вы знаете, о чем говорите; надеюсь, однажды я лучше пойму, как все это работает. Спасибо за помощь. - person Jean-Luc; 01.02.2012

Чтобы приостановить рабочий поток, я использовал следующий подход.

Вот часть моего файла GUI.h:

public:
    QAtomicInt check;   //it has to be public to be reachable from a
                        //working thread; we’ll use it as a pause flag
private:
    int receiver;       //internal flag
    QThread *thread;    //we will use thread, won’t we?
    Worker *worker;     //here is where all the work is done
signals:
    void begin();       //we will also need a signal

Вот часть моего файла GUI.cpp:

Widget::Widget(){
    receiver = 0;
    check = QAtomicInt(1);    //you may use any number, even 42
    pb = new QPushButton("Start");    //I used a button to start, 
                                    //suspend and resume a working thread
    connect(pb, SIGNAL(clicked()), this, SLOT(start()));
    thread = new QThread;    //who did not read Maya Posch’s blog?
    worker = new Worker(this);    //we need a pointer to this to reach
              //our check flag, remember?
    worker->moveToThread(thread);
    connect(this, SIGNAL(begin()), worker, SLOT(compute()));
    connect(worker, SIGNAL(over()), this, SLOT(ovr()));
    thread->start();
}

void Widget::start() {
    if ( receiver == 0 ) {    //just to remember where we are
        pb->setText("Stop");
        receiver = 1;
        emit begin();    //here we start our heavy job
    } else if ( receiver == 1 ) {    //here we pause it
        pb->setText("Start");
        receiver = 2;
        while ( !(check.testAndSetOrdered(2, 3))) {}

//this is where all the magic is done testAndSetOrdered 
//may fail so we repeat it until it succeeds

    } else {
        pb->setText("Stop");
        receiver = 1;
        while ( !(check.testAndSetOrdered(3, 1))) {}

//first we have to restore check to its normal value. 
//This time we can almost never fail, but just in case 
//I leave the while block here

        emit begin();    //here we resume our job
    }
}

Вот мой файл worker.h:

class Worker : public QObject {    //do not ask why I did not inherit from QThread, 
                                   //just read Maya Posch
    Q_OBJECT
public:
    Worker(Widget*);
public slots:
    void compute();    //the only slot here that does it all
signals:
    void over();       //we have to inform the GUI thread that we are over
private:
    int limit, counter;    //it is important to declare counter
    Widget *parent;
};

Вот часть моего файла worker.cpp:

Worker::Worker(Widget* par) {
    parent = par;    //store a pointer to the GUI thread
    counter = 1;     //it is important to initialize counter HERE
    limit = 100000000;
}

void Worker::compute() {
    while ( counter < limit ) {
        if ( parent->check.testAndSetOrdered(1, 2) ) {  //THERE

//testAndSetOrdered may fail, if check was set to another value in the GUI thread.
//If this is the case, we return and DO NOTHING. Compared to techniques with wait() 
//and QMutex and QWaitCondition, this approach is easier on CPU.

            //do your calculations HERE
            counter += 1;
            parent->check.testAndSetOrdered(2, 1);    

//before the next iteration we have to restore
//check to 1, and we don’t care if we fail here

        } else {
            return;
        }
    }

//now we get ready for yet another round of calculations and inform the GUI 
//thread that we are over with this round.

    counter = 1;
    emit over();
}

Основная идея заключается в использовании специальных функций QAtomicInt. В рабочем потоке проверяем, не изменился ли CHECK. Если он был изменен, мы возвращаемся и ничего не делаем. Чтобы изменить его, мы должны конкурировать с рабочим потоком за доступ к CHECK из потока GUI. Вот почему нам нужен блок while. Мы ставим блок while в разделе возобновления, хотя в большинстве случаев это удается с первой попытки. Но мы имеем дело с многопоточностью, помните?

person alexencon    schedule 08.04.2013