QThread Потоки ( как правильно закрыть поток )
Привет всем! накидал маленький рабочий пример за 5 минут, но возник вопрос, как правильно закрыть поток? При старте создаётся новый поток, а старый сохраняется. Возможно, что то не учёл или сделал не так. Вот исходники: main.cpp
#include "mainwindow.h" #include int main(int argc, char *argv[])
mainwindow.cpp
#include "mainwindow.h" #include "ui_mainwindow.h" #include #include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) < ui->setupUi(this); connect(this->ui->pushButton_start, SIGNAL(clicked()), this, SLOT(startGUI())); connect(this->ui->pushButton_stop, SIGNAL(clicked()), this, SLOT(stopGUI())); > MainWindow::~MainWindow() < delete ui; >void MainWindow::startGUI() < // Создание потока QThread* thread = new QThread; Worker* worker = new Worker(); // Передаем права владения "рабочим" классом, классу QThread. worker->moveToThread(thread); //connect(worker, SIGNAL(sendBool(bool)), this, SLOT(stopGUI(bool))); // Связываем сигнал об ошибки со слотом обработки ошибок(не показан). //connect(worker, SIGNAL(error(QString)), this, SLOT(errorHandler(QString))); // Соединяем сигнал started потока, со слотом process "рабочего" класса, т.е. начинается выполнение нужной работы. connect(thread, SIGNAL(started()), worker, SLOT(process())); // Отображаем в главном потоке Gui, значения из вторичного потока connect(worker, SIGNAL(sendNumber(int)), this, SLOT(LineEditUi(int))); // Оповещаем поток, что нужно остановиться connect(this, SIGNAL(sendNumberBoolStop(bool)), worker, SLOT(reciveBoolStop(bool)), Qt::DirectConnection); // По завершению выходим из потока, и удаляем рабочий класс connect(worker, SIGNAL(finished()), thread, SLOT(quit())); connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater())); // Удаляем поток, после выполнения операции connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); thread->start(); > void MainWindow::LineEditUi(int number) < ui->lineEdit->setText(QString::number(number)); > void MainWindow::stopGUI() < Stop = true; qDebug() class MainWindow : public QMainWindow < Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); bool Stop; public slots: void startGUI(); void stopGUI(); void LineEditUi(int number); signals: void sendNumberBoolStop(bool); private: Ui::MainWindow *ui; >; #endif // MAINWINDOW_H
#include "worker.h" #include Worker::Worker(QObject *parent) : QObject(parent) < Stop = false; temp = 0; >Worker::~Worker() < qDebug() void Worker::process() < Stop = false; if(!Stop == true) < for (temp; temp lineEdit->setText(QString::number(temp)); //QCoreApplication::processEvents(); qDebug() else < return; >> > > void Worker::reciveBoolStop(bool Numb) < Stop = Numb; qDebug() MainWindow 0 0 395 165 MainWindow -
-
-
Start -
Stop 0 0 395 21 TopToolBarArea false
Как остановить поток Qt с очисткой ресурсов
Добрый день. Создаю поток, наследуясь от QThread (на хабре статью читал, что так не надо). В потоке в методе run создают динамический объект. Как мне можно остановить этот поток, при этом что бы объект был удален и вызван метод объекта для закрытия ресурсов. Смотрел варианты когда в run крутится вечный цикл
while( !flStop ) < //do work >
но у меня все построено на событиях
MyThread::run() < mSerial = new QSerialPort; mSerial->open(); //вопрос что если не откроем, пока оставим exec(); >
, приходят сигналы и вызываются соответствующие им методы объекта, т.е. если я попадаю в метод потока где удаляю ресурсы
MyThread::clear() < mSerial->close(); delete mSerial; >
то мне надо и тормозить тут же поток, т.к. может прийти вызов другого метода
MyThread::write(QByteArray someBytes) < mSerial->write(someBytes); >
а mSerial уже удален, т.е. если я все правильно понимаю, метод clear должен быть финальным и в нем необходимо себя останавливать, либо эмитить сигнал и заставлять другой объект останавливать MyThread, но если я заэмичу сигнал, то где гарантии, что мне не прилетит сигнал на запись в mSerial. Т.е. получается при таком подходе я должен в clear и тормозить сам себя методом quit(). Такой вот вопрос
da17 ★
25.01.19 14:03:09 MSK

man QEventLoop QWaitCondition QMutex потоки
pon4ik ★★★★★
( 25.01.19 14:19:27 MSK )

эмитить сигнал и заставлять другой объект останавливать MyThread
[br]MyThread::clear() close();[br] delete mSerial;[br] mSerial = nullptr;[br] emit stopMe();[br]>[br]
[br]MyThread::write(QByteArray someBytes) write(someBytes);[br]>[br]
robus ★★★★★
( 25.01.19 14:32:50 MSK )
Ответ на: комментарий от robus 25.01.19 14:32:50 MSK
Ага, я понял, спасибо. Изящно. Но вот именно в моем случае, это будет ок, если я прям в clear тормозну себя.
da17 ★
( 25.01.19 14:36:18 MSK ) автор топика
Ответ на: комментарий от robus 25.01.19 14:32:50 MSK

А что будет, если mSerial таки удалится сразу после того как условие выполнится, но до записи, или сразу после разыменования?
pon4ik ★★★★★
( 25.01.19 14:59:21 MSK )
Ответ на: комментарий от da17 25.01.19 14:36:18 MSK

Сорри за «br», интернет забаговал именно в момент редактирования. User line break с тегом коде ведёт себя просто идеально, да LOR?
robus ★★★★★
( 25.01.19 15:00:05 MSK )
Ответ на: комментарий от robus 25.01.19 15:00:05 MSK

Так наверное такие же знатоки в юзабилити делали, как ты в многопоточном программировании 🙂 И да, превью никто не отменял.
pon4ik ★★★★★
( 25.01.19 15:01:55 MSK )
Ответ на: комментарий от pon4ik 25.01.19 14:59:21 MSK

Так и clear и write ведь обработчики событий, а значит выполнятся в одном и том же потоке — в потоке MyThread. А значит одно не начнется, пока другое не закончилось.
robus ★★★★★
( 25.01.19 15:02:23 MSK )
Ответ на: комментарий от robus 25.01.19 15:02:23 MSK

Ну да, пока кто-то не приконнектится к этому потоку не функой по умолчанию, хм. На каком нить уровне иеррархии, где даже не известно, что это поток. Нет уж, не должна инкапсулированая сущность зависеть от метода её вызова.
pon4ik ★★★★★
( 25.01.19 15:05:42 MSK )
mSerial = new QSerialPort(this) — и удалять самому не надо
x905 ★★★★★
( 25.01.19 15:20:01 MSK )
Ответ на: комментарий от pon4ik 25.01.19 15:05:42 MSK

Ну да, пока кто-то не приконнектится к этому потоку не функой по умолчанию, хм. На каком нить уровне иеррархии, где даже не известно, что это поток
Этого не может быть, потому что быть не может. А ещё потому, что в реализации QThread таки написано в начале Q_OBJECT .
Но даже если бы Q_OBJECT там не было, известно что это наследник QObject (иначе ты попросту не приконнектишь). А дальше Qt обеспечит кьюинг обработчиков. А если коннектишь через std::bind или лямды, то ССЗБ.
Но даже если бы Qt не обеспечивал, то ТС нигде не говорил, что это вот все находится в публичном интерфейсе либы, а значит все места, где коннектится подконтрольны.
Нет уж, не должна инкапсулированая сущность зависеть от метода её вызова.
Если это пойдет на пользу производительности, то запросто может и зависеть. Параллелят всегда ради производительности и отзывчивости.
Но ты можешь на каждый чих лочить мутекс, ждать пока рак на горе свистнет и т.д. И будешь таким же спецом в параллельном программировании, каким был бы спецом в управлении памятью гипотетический чувак, завернувший абсолютно всё в std::shared_ptr , вместо стека, виков и уников. Безопасно, но не оптимально.
robus ★★★★★
( 25.01.19 15:20:30 MSK )

В общем подытожив — exit() в очистке нормальный вариант(не quit). Но всё равно, без блокировки не защитит от кривых рук клиента.
pon4ik ★★★★★
( 25.01.19 15:21:07 MSK )
Ответ на: комментарий от robus 25.01.19 15:20:30 MSK

А фьютекс наносекунды стоит если всё ок.
pon4ik ★★★★★
( 25.01.19 15:23:11 MSK )
Ответ на: комментарий от robus 25.01.19 15:20:30 MSK

Если не веришь мне и докам, можешь глянуть сорцы QEventLoop, и в частности то, как выбирается текущий цикл обработки событий.
pon4ik ★★★★★
( 25.01.19 15:25:37 MSK )
Ответ на: комментарий от robus 25.01.19 15:20:30 MSK

И алыверды, для тех кто боится мьютексов 🙂 Можно посмотреть, что происходит при Auto соединении. Там как раз всегда проверяется какому потоку принадлежит объект(раз лок), если не текущему лочится обьект треда(два лок), если в нём запущен цикл обработки событий, то лочится его очередь(три лок) иначе вроде лочится главный цикл и сообщение попадает туда(точно не помню уже). А вот если в ручную тип соединения указать, можно немного сэкономить на спичках.
pon4ik ★★★★★
( 25.01.19 15:35:58 MSK )
Последнее исправление: pon4ik 25.01.19 15:36:41 MSK (всего исправлений: 1)

Кстати про Auto соединение, мне лень читать, но скорее всего, нужно ещё обьекту твоего потока сделать moveToThread, что бы всё работало, как ты задумываешь. Т.к. создаётся сам инстанс QThread в основном потоке. А вот мувит ли он сам себя — х3, я бы не стал например.
pon4ik ★★★★★
( 25.01.19 15:40:31 MSK )

на хабре статью читал, что так не надо
А что это за статья?
Xintrea ★★★★★
( 25.01.19 15:40:45 MSK )
Ответ на: комментарий от pon4ik 25.01.19 15:35:58 MSK

Можно посмотреть, что происходит при Auto соединении. Там как раз всегда проверяется какому потоку принадлежит объект(раз лок), если не текущему лочится обьект треда(два лок), если в нём запущен цикл обработки событий, то лочится его очередь(три лок) иначе вроде лочится главный цикл и сообщение попадает туда(точно не помню уже)
Это те расходы, которые ТС собрался нести.
но у меня все построено на событиях
Чтобы избавиться от них ему придётся переписать всё на QtConcurrent, или, на худой конец, переписать MyThread на бесконечный цикл. Если тебе интересно моё мнение, то да, это стоит сделать.
А вот если в ручную тип соединения указать, можно немного сэкономить на спичках.
Поэтому, я указываю. Но уж никак не Qt::DirectConnection , где автоматика бы выставила Qt::QueuedConnection . Qt::DirectConnection ставлю только тогда, когда точно знаю, что обработчик нужно выполнить в том же потоке, где заэмитился сигнал.
P.S. не нужно так сильно хейтить реентрабельность и везде насаживать полную тредсафе.
robus ★★★★★
( 25.01.19 15:49:25 MSK )
Ответ на: комментарий от pon4ik 25.01.19 15:40:31 MSK

нужно ещё обьекту твоего потока сделать moveToThread, что бы всё работало, как ты задумываешь
Да ладна, как жи так та?!
Т.к. создаётся сам инстанс QThread в основном потоке
А вот мувит ли он сам себя — х3
Нет не мувит. Это делает код ТС, коль скоро тут вопрос об остановке потока, а не «Помогите, не работает многопоточность в Qt5».
robus ★★★★★
( 25.01.19 15:51:43 MSK )
Последнее исправление: robus 25.01.19 15:52:19 MSK (всего исправлений: 1)
Ответ на: комментарий от robus 25.01.19 15:49:25 MSK

Это те расходы, которые ТС собрался нести.
Это те расходы, которые решает нести или нет продвинутый пользователь api. Внутреннее оно или внешнее особой роли не играет с ростом проекта. А экономия на локе в современных(последние лет 10) системах — нужна только когда ты начала считать сотни наносекунд. Что в gui приложения огромная редкость (лично я ни разу не видел). Если драки на блокировке не происходит, то она стоит как пара разыменовываний указателей, особенно если мимо кэша.
pon4ik ★★★★★
( 25.01.19 15:53:06 MSK )
Ответ на: комментарий от robus 25.01.19 15:49:25 MSK

Если тебе интересно моё мнение, то да, это стоит сделать.
Проделай, лучше когда нибудь упражнение — свой бесконечный цикл и QEventLoop и глянь у кого окажется трупут выше и лэйтенси ниже. Я бы со всеми накладными расходами поставил таки на кутишников, т.к. эта горячая точка годами оптимизируется не глупыми товарищами 🙂
pon4ik ★★★★★
( 25.01.19 16:01:41 MSK )
Последнее исправление: pon4ik 25.01.19 16:01:56 MSK (всего исправлений: 1)
Ответ на: комментарий от pon4ik 25.01.19 15:53:06 MSK

А экономия на локе в современных(последние лет 10) системах — нужна только когда ты начала считать сотни наносекунд. Что в gui приложения огромная редкость (лично я ни разу не видел).
Ну давай, забей на сотни наносекунд в paintEvent-е своей кастомной, красивенькой в цветах твоего проекта/компании, кнопочки. Которых на всех формах всех окон сотни. Вангую, что с таким подходом ты вскоре придёшь к стойкому убеждению, что QPainter тормозит, и вообще с графикой в линухе полная габелла, нативная кроссплатформа мертва, Qt разжирел и не торт больше вовсе.
robus ★★★★★
( 25.01.19 16:02:11 MSK )
Ответ на: комментарий от pon4ik 25.01.19 16:01:41 MSK

Во! Есть чем вечер скоротать 🙂
robus ★★★★★
( 25.01.19 16:03:02 MSK )
Ответ на: комментарий от robus 25.01.19 16:02:11 MSK

100 * 100 = 10 000 = 10us.
Пожалуй я забью.
pon4ik ★★★★★
( 25.01.19 16:03:29 MSK )

метод объекта для закрытия ресурсов
erfea ★★★★★
( 25.01.19 16:03:31 MSK )
Ответ на: комментарий от pon4ik 25.01.19 16:03:29 MSK

robus ★★★★★
( 25.01.19 16:07:07 MSK )
Ответ на: комментарий от robus 25.01.19 16:07:07 MSK

Считал я эти микросекунды. Одно дело когда какой нить софт риалтайм с 10г сетевухами, а другое дело когда ui поток без логики с самым распрекрасным opengl на борту спит 50% времени даже если пользователь бьётся в эпилептических припадках в обнимку с клавой и мышью.
Если что, моник моргает с частотами порядка миллисекунды.
pon4ik ★★★★★
( 25.01.19 16:10:51 MSK )

Т.е. получается при таком подходе я должен в clear и тормозить сам себя методом quit()
annulen ★★★★★
( 25.01.19 18:47:51 MSK )
Ответ на: комментарий от pon4ik 25.01.19 16:01:41 MSK

Сигналы сосут
23:10:16: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark. Qt QObject worker: 83,36900 ms Velosiped (TM): 6,79000 ms Qt Concurrent: 4,81400 ms 23:10:16: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0 23:10:21: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark. Qt QObject worker: 83,96800 ms Velosiped (TM): 6,02300 ms Qt Concurrent: 4,02200 ms 23:10:21: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0 23:10:21: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark. Qt QObject worker: 93,91400 ms Velosiped (TM): 6,81400 ms Qt Concurrent: 3,79700 ms 23:10:22: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0 23:10:22: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark. Qt QObject worker: 98,48000 ms Velosiped (TM): 7,08200 ms Qt Concurrent: 4,88500 ms 23:10:22: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0 23:10:23: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark. Qt QObject worker: 86,67700 ms Velosiped (TM): 6,75400 ms Qt Concurrent: 3,71200 ms 23:10:23: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0 23:10:23: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark. Qt QObject worker: 100,09700 ms Velosiped (TM): 7,41600 ms Qt Concurrent: 3,82200 ms 23:10:23: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0 23:10:24: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark. Qt QObject worker: 95,05300 ms Velosiped (TM): 6,74600 ms Qt Concurrent: 5,24600 ms 23:10:24: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0 23:10:25: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark. Qt QObject worker: 93,84800 ms Velosiped (TM): 6,14900 ms Qt Concurrent: 3,94100 ms 23:10:25: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0 23:10:25: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark. Qt QObject worker: 90,41500 ms Velosiped (TM): 6,99000 ms Qt Concurrent: 5,09000 ms 23:10:25: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0 23:10:26: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark. Qt QObject worker: 94,83900 ms Velosiped (TM): 6,74600 ms Qt Concurrent: 5,30200 ms 23:10:26: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0 23:10:26: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark. Qt QObject worker: 87,92100 ms Velosiped (TM): 5,99300 ms Qt Concurrent: 5,18300 ms 23:10:27: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0 23:10:27: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark. Qt QObject worker: 89,82200 ms Velosiped (TM): 5,82700 ms Qt Concurrent: 4,72100 ms 23:10:27: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0 23:10:28: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark. Qt QObject worker: 88,11900 ms Velosiped (TM): 6,96300 ms Qt Concurrent: 5,10000 ms 23:10:28: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0
robus ★★★★★
( 25.01.19 19:09:38 MSK )
Последнее исправление: robus 25.01.19 19:11:08 MSK (всего исправлений: 1)
Ответ на: комментарий от annulen 25.01.19 18:47:51 MSK
Подвох в том, что непонятно когда можно удалять поток. Вот есть где-то объект которые владеет указателем на поток. Я так понимаю, что когда мы удалили ресурсы, остановили поток, можно эмитить сигнал, что теперь объект потока можно и удалить.
connect( mCommander, SIGNAL(stop_thread(), mMyThread, SLOT(clear())); connect(mMyThread, SIGNAL(thread_stopped, mCommander, SLOT(stop_confirmed() ));
Тут после закрытия ресурсов и остановки потока эмитим, что мы все почистили и остановили поток, можно нас удалить.
MyThread::clear() < mSerial->close(); delete mSerial; quit(); emit thread_stopped(); > void Commander::stopComThread() < emit stop_thread(); //приходим в clear mMyThread >void Commander::stop_confirmed() < //Тут уж можно и удалять delete mMyThread; >
da17 ★
( 25.01.19 19:13:33 MSK ) автор топика
Ответ на: комментарий от da17 25.01.19 19:13:33 MSK

Подвох в том, что непонятно когда можно удалять поток.
deleteLater() себе, если это не может сделать вызывающая сторона
annulen ★★★★★
( 25.01.19 19:28:37 MSK )
Ответ на: комментарий от da17 25.01.19 19:13:33 MSK

И вообще после quit() сам поток уже уничтожен, осталась только шкурка в виде объекта QThread
annulen ★★★★★
( 25.01.19 19:30:39 MSK )
Ответ на: комментарий от annulen 25.01.19 19:30:39 MSK
Но он же может еще некоторое время останавливаться? wait разве не для этого?
da17 ★
( 25.01.19 19:32:04 MSK ) автор топика
Ответ на: Сигналы сосут от robus 25.01.19 19:09:38 MSK

Ну, раз сосут ничего тут не поделаешь. Правда не очень мне понятно почему в коде, который соревнуется с циклом обработки сообщений не используется ни семафоров ни условных переменных, но наверное так можно.
pon4ik ★★★★★
( 25.01.19 19:37:07 MSK )
Ответ на: комментарий от da17 25.01.19 19:32:04 MSK

Смотря в каком потоке вызывается MyThread::clear() — я предполагал, что она выполняется в самом потоке, тогда quit() приводит к выходу из QEventLoop и немедленному завершению главной функции потока
annulen ★★★★★
( 25.01.19 19:42:09 MSK )
Ответ на: комментарий от annulen 25.01.19 19:42:09 MSK
Именно, да в самом потоке. Если будет выход из QEventLoop, я смогу отослать сигнал (свой) или все уже.
da17 ★
( 25.01.19 19:46:09 MSK ) автор топика
Ответ на: комментарий от da17 25.01.19 19:46:09 MSK

Если будет выход из QEventLoop, я смогу отослать сигнал
Queued connection обрабатывается в QEventLoop получателя, а не отправителя
Qthread как остановить поток
Шаг 15.
Библиотека PyQt5. Знакомство с PyQt5. Многопоточные приложения. Управление циклом внутри потока
На этом шаге мы рассмотрим особенности такого управления .
Очень часто внутри потока одни и те же инструкции выполняются многократно. Например, при осуществлении мониторинга серверов в Интернете на каждой итерации цикла посылается запрос к одному и тому же серверу. При этом внутри метода run() используется бесконечный цикл, выход из которого производится после окончания опроса всех серверов. В некоторых случаях этот цикл необходимо прервать преждевременно по нажатию кнопки пользователем. Чтобы это стало возможным, в классе, реализующем поток, следует создать атрибут, который будет содержать флаг текущего состояния. Далее на каждой итерации цикла мы проверяем состояние флага и при его изменении прерываем выполнение цикла. Чтобы изменить значение атрибута, создаем обработчик и связываем его с сигналом clicked() соответствующей кнопки. При нажатии кнопки внутри обработчика производим изменение значения атрибута. Пример запуска и остановки потока с помощью кнопок.
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets class MyThread(QtCore.QThread): mysignal = QtCore.pyqtSignal(str) def __init__(self, parent = None): QtCore.QThread.__init__(self, parent) self.running = False # Флаг выполнения self.count = 0 def run(self): self.running = True while self.running: # Проверяем значение флага self.count += 1 self.mysignal.emit("count = %s" % self.count) self.sleep(1) # Имитируем процесс class MyWindow(QtWidgets.QWidget): def __init__(self, parent = None): QtWidgets.QWidget.__init__(self, parent) self.label = QtWidgets.QLabel("Нажмите кнопку для запуска потока") self.label.setAlignment(QtCore.Qt.AlignHCenter) self.btnStart = QtWidgets.QPushButton("Запустить поток") self.btnStop = QtWidgets.QPushButton("Остановить поток") self.vbox = QtWidgets.QVBoxLayout() self.vbox.addWidget(self.label) self.vbox.addWidget(self.btnStart) self.vbox.addWidget(self.btnStop) self.setLayout(self.vbox) self.mythread = MyThread() self.btnStart.clicked.connect(self.on_start) self.btnStop.clicked.connect(self.on_stop) self.mythread.mysignal.connect(self.on_change, QtCore.Qt.QueuedConnection) def on_start(self): if not self.mythread.isRunning(): self.mythread.start() # Запускаем поток def on_stop(self): self.mythread.running = False # Изменяем флаг выполнения def on_change(self, s): self.label.setText(s) def closeEvent(self, event): # Вызывается при закрытии окна self.hide() # Скрываем окно self.mythread.running = False # Изменяем флаг выполнения self.mythread.wait(5000) # Даем время, чтобы закончить event.accept() # Закрываем окно if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.setWindowTitle("Запуск и остановка потока") window.resize(300, 70) window.show() sys.exit(app.exec_())
Архив с файлом можно взять здесь.
Результат работы приложения изображен на рисунке 1.
Рис.1. Результат работы приложения
В этом примере внутри конструктора класса MyThread создается атрибут running , и ему присваивается значение False . При запуске потока внутри метода run() значение атрибута изменяется на True . Затем запускается цикл, в котором атрибут указывается в качестве условия. Как только значение атрибута станет равным значению False , цикл будет остановлен.
Внутри конструктора класса MyWindow производится создание надписи, двух кнопок и экземпляра класса MyThread . Далее назначаются обработчики сигналов. При нажатии кнопки Запустить поток запустится метод on_start() , внутри которого с помощью метода isRunning() производится проверка текущего статуса потока. Если поток не запущен, выполняется его запуск вызовом метода start() . При нажатии кнопки Остановить поток запустится метод on_stop() , в котором атрибуту running присваивается значение False . Это значение является условием выхода из цикла внутри метода run() .
Путем изменения значения атрибута можно прервать выполнение цикла только в том случае, если закончилось выполнение очередной итерации. Если поток длительное время ожидает какого-либо события, — например, ответа сервера, то можно так и не дождаться завершения потока. Чтобы принудительно прервать выполнение потока, следует воспользоваться методом terminate() . Однако к этому методу следует прибегать только в крайнем случае, поскольку прерывание производится в любой части кода. При этом блокировки автоматически не снимаются, а кроме того, можно повредить данные, над которыми производились операции в момент прерывания. После вызова метода terminate() следует вызвать метод wait() .
При закрытии окна приложение завершает работу, что также приводит к завершению всех потоков. Чтобы предотвратить повреждение данных, мы перехватываем событие закрытия окна и дожидаемся окончания выполнения. Чтобы перехватить событие, необходимо внутри класса создать метод с предопределенным названием, в нашем случае — с названием closeEvent() . Этот метод будет автоматически вызван при попытке закрыть окно. В качестве параметра метод принимает объект события event , через который можно получить дополнительную информацию о событии. Чтобы закрыть окно внутри метода closeEvent() , следует вызвать метод accept() объекта события. Если необходимо предотвратить закрытие окна, то следует вызвать метод ignore() .
Внутри метода closeEvent() мы присваиваем атрибуту running значение False . Далее с помощью метода wait() даем возможность потоку нормально завершить работу. В качестве параметра метод wait() принимает количество миллисекунд, по истечении которых управление будет передано следующей инструкции. Необходимо учитывать, что это максимальное время: если поток закончит работу раньше, то и метод закончит выполнение раньше. Метод wait() возвращает значение True , если поток успешно завершил работу, и False — в противном случае. Ожидание завершения потока занимает некоторое время, в течение которого окно будет по-прежнему видимым. Чтобы не вводить пользователя в заблуждение, в самом начале метода closeEvent() мы скрываем окно вызовом метода hide() .
На следующем шаге мы закончим изучение этого вопроса .
Qthread как остановить поток
Но это не всегда возможно.
Почему я вообще задал вопрос. Если заводить флаг, то в объекте потоке нужно его хранить и нужно заводить метод для его сброса.
Кроме этого нужно будет либо в документации указывать, что для остановки нужно дергать именно этот метод (иначе поток не остановить)
либо закрыто наследоваться и открывать только нужные методы.
Не крутовато для простого потока?
Сообщ. #6 , 13.09.12, 03:47

Рейтинг (т): 77
Цитата xbarmaglot @ 12.09.12, 15:50
для этого нужно заводить метод для сброса флага.
разумеется.
Цитата xbarmaglot @ 12.09.12, 15:50
Разве стандартного ничего не предусмотрено ?
принудительное завершение потока — ни разу не стандартное желание.
Цитата xbarmaglot @ 12.09.12, 18:00
Не крутовато для простого потока?
это и есть тот самый стандартно используемый способ.
Сообщ. #7 , 13.09.12, 06:25
Рейтинг (т): 2
Цитата niXman @ 13.09.12, 03:47
принудительное завершение потока — ни разу не стандартное желание.
я имел ввиду, что поток как правило крутится в бесконечном цикле и его нужно корректно останавливать.
Если создавать потоковый объект как предложил Adil, то не совсем понятно как запустить QThread::exec,
так как после входа в run там будет цикл из которого не выйти.
Может как-то прикрутить QRunnable?
Или я не совсем понимаю принцип использования потока.
Вот простейший пример. Есть очередь — поток должен ее вычитывать пока его не остановят
и передавать данные на обработку
Сообщ. #8 , 13.09.12, 10:44

Рейтинг (т): 77
Цитата xbarmaglot @ 13.09.12, 06:25
я имел ввиду, что поток как правило крутится в бесконечном цикле и его нужно корректно останавливать.
Если создавать потоковый объект как предложил Adil, то не совсем понятно как запустить QThread::exec,
так как после входа в run там будет цикл из которого не выйти.
как-то сумбурно описано.