c
cyberaxe7718 ноября 2019 г. 5:21

PyQt5 QThread периодическая проверка доступности соединения MySql в отдельном потоке

QThread

Здравствуйте форумчане.
Собственно решил побаловаться с потоками в PyQt5. Придумал небольшую учебную задачку для себя по данной теме.
Форма авторизации для MySql. Всё стандартно: user - password в QLineEdit, кнопки "connect", "disconnect" и QLabel сообщающая о статусе соединения. Форма должна проверять возможность соединения с сервером 1 раз в 3 секунды. Для этого я в метод run() класса QThread поместил соединение с БД.

#!/usr/bin/python3
# -*- coding:utf-8 -*-

import sys
import UI_MainForm
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtSql import *


class MyThread(QThread):
    mysignal = pyqtSignal(str)

    def __init__(self, _user=None, _passwd=None, parent=None):
        self.user = _user
        self.passwd = _passwd
        QThread.__init__(self, parent)

    def run(self):
        while True:
            self.sleep(3)
            db = QSqlDatabase.addDatabase('QMYSQL')
            db.setHostName('localhost')
            db.setDatabaseName('testbase')
            db.setUserName(self.user)
            db.setPassword(self.passwd)
            ok = db.open()
            if ok:
                self.mysignal.emit('ok')
                print('Debug: Connection: Ok')
                db.close()
            else:
                self.mysignal.emit('failed')
                print('Debug: Connection: Failed ' + db.lastError().text())


class MainForm(QMainWindow, UI_MainForm.Ui_MainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.setupUi(self)

        self.mythread = MyThread()

        self.pushButton_quit.clicked.connect(qApp.quit)
        self.pushButton_connect.clicked.connect(self.connect_db)
        self.pushButton_disconnect.clicked.connect(self.disconnect_db)
        self.pushButton_connect.clicked.connect(self.start_thread)
        self.mythread.mysignal.connect(self.change_label_conn_status, Qt.QueuedConnection)

    def connect_db(self):
        db = QSqlDatabase.addDatabase('QMYSQL')
        db.setHostName('localhost')
        db.setDatabaseName('testbase')
        db.setUserName(self.lineEdit_user.text())
        db.setPassword(self.lineEdit_passwd.text())
        ok = db.open()
        if ok:
            self.label_connection_status.setText('Connection status: Ok')
            QMessageBox.information(self, 'Information', 'Successful database connection')
        else:
            self.label_connection_status.setText('Connection status: Failed')
            QMessageBox.critical(self, 'ERROR!', 'No database connection!')

    def disconnect_db(self):
        self.pushButton_connect.setEnabled(True)
        self.lineEdit_user.setEnabled(True)
        self.lineEdit_passwd.setEnabled(True)
        self.lineEdit_user.clear()
        self.lineEdit_passwd.clear()
        self.lineEdit_user.setFocus()

    def change_label_conn_status(self, s):
        self.label_connection_status.setText('Connection status: ' + s)    #текст не меняется

    def start_thread(self):
        self.pushButton_connect.setEnabled(False)
        self.lineEdit_user.setEnabled(False)
        self.lineEdit_passwd.setEnabled(False)
        self.mythread = MyThread(self.lineEdit_user.text(), self.lineEdit_passwd.text())
        self.mythread.start()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MainForm()
    win.show()
    sys.exit(app.exec_())

Собственно проблемы:
1. При дауне MySql, label_connection_status не изменяется на "failed", хотя в консоль метод run() гадит print('Debug: Connection: Failed ' + db.lastError().text()), как и было задумано. Почему?
2. Два потока (в конструкторе и в методе start_thread) - бред. Как избавиться от этого бреда? Как сделать правильно?
3. Может быть можно как-то иначе реализовать проверку доступности MySql для соединения?

Заранее благодарю.

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

15
c
  • 18 ноября 2019 г. 10:22

Вроде бы "пофиксил", но буду рад любым замечаниям по этому говнокоду)))

#!/usr/bin/python3
# -*- coding:utf-8 -*-

import sys
import UI_MainForm
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtSql import *


class MyThread(QThread):
    mysignal = pyqtSignal(str)

    def __init__(self, _user=None, _passwd=None, parent=None):
        self.user = _user
        self.passwd = _passwd
        QThread.__init__(self, parent)

    def run(self):
        while True:
            self.sleep(3)
            db = QSqlDatabase.addDatabase('QMYSQL')
            db.setHostName('localhost')
            db.setDatabaseName('testbase')
            db.setUserName(self.user)
            db.setPassword(self.passwd)
            ok = db.open()
            if ok:
                self.mysignal.emit('Ok')
                print('Debug: Connection: Ok')
                db.close()
            else:
                self.mysignal.emit('Failed')
                print('Debug: Connection: Failed ' + db.lastError().text())


class MainForm(QMainWindow, UI_MainForm.Ui_MainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.setupUi(self)

        self.pushButton_quit.clicked.connect(qApp.quit)
        self.pushButton_connect.clicked.connect(self.connect_db)
        self.pushButton_disconnect.clicked.connect(self.disconnect_db)
        self.pushButton_connect.clicked.connect(self.start_thread)

    def connect_db(self):
        db = QSqlDatabase.addDatabase('QMYSQL')
        db.setHostName('localhost')
        db.setDatabaseName('testbase')
        db.setUserName(self.lineEdit_user.text())
        db.setPassword(self.lineEdit_passwd.text())
        ok = db.open()
        if ok:
            self.label_connection_status.setText('Connection status: Ok')
            QMessageBox.information(self, 'Information', 'Successful database connection')
        else:
            self.label_connection_status.setText('Connection status: Failed')
            QMessageBox.critical(self, 'ERROR!', 'No database connection!')

    def disconnect_db(self):
        self.pushButton_connect.setEnabled(True)
        self.lineEdit_user.setEnabled(True)
        self.lineEdit_passwd.setEnabled(True)
        self.lineEdit_user.clear()
        self.lineEdit_passwd.clear()
        self.lineEdit_user.setFocus()

    def change_label_conn_status(self, s):
        self.label_connection_status.setText('Connection status: ' + s)

    def start_thread(self):
        self.pushButton_connect.setEnabled(False)
        self.lineEdit_user.setEnabled(False)
        self.lineEdit_passwd.setEnabled(False)
        self.mythread = MyThread(self.lineEdit_user.text(), self.lineEdit_passwd.text())
        self.mythread.mysignal.connect(self.change_label_conn_status, Qt.QueuedConnection)
        self.mythread.start()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MainForm()
    win.show()
    sys.exit(app.exec_())
    B
    • 19 ноября 2019 г. 4:33

    Я не знаю правильно ли я делаю, но я сигнал для потока не создаю,
    я в поток передаю ссылку на то куда надо результат выводить/изменить, у меня в основном текстовое поле и я туда передаю кучу информации из потока в разных цветах и т.д.
    Я в поток много чего передаю, даже ссылку на соединение UDP, созданное в основном приложении и работает отлично, т.е. из потока проверяет и шлет что то и т.д.
    Следует отметить у меня в основном поток один, кроме основной формы, когда два и более и если пересекаются я делаю флаги занятости ресурса к которому обращаются сразу несколько потоков, к примеру список, по очереди пускаю туда складывать данные потоков, привязываю очередность к времени вызова.

    Где бы на примерах посмотреть как правильно потоками рулить???
    Примеры какие то везде примитивные.
    А то сам что то фантазирую...

      c
      • 19 ноября 2019 г. 4:49

      Я штудирую небезизвестную книгу Прохорёнка и Дронова.
      Будьте добры скинуть один-два примера с передачей ссылок в поток. Очень интересно. Заранее благодарю.

        B
        • 19 ноября 2019 г. 4:57

        у меня эта книга то же есть про потоки там мизер совершенно, примеры сегодня накидаю вечером, попробуйте в вашем примере в конструкторе потока принять ссылку на label, и в потоке все Print отправляйте в нее. При создании потока передайте ему созданную label, без всяких сигналов.

          Evgenii Legotckoi
          • 19 ноября 2019 г. 5:04
          • (ред.)

          label - это GUI элемент в данном случае? Если так, то я бы не стал раскидывать GUI элементы в разные потоки. Дело в том, что в документации на Qt, сказано, что GUI элементы работают только в GUI-потоке и не работают в других потоках. Цитирую

          As mentioned, each program has one thread when it is started. This thread is called the "main thread" (also known as the "GUI thread" in Qt applications). The Qt GUI must run in this thread. All widgets and several related classes, for example QPixmap, don't work in secondary threads. A secondary thread is commonly referred to as a "worker thread" because it is used to offload processing work from the main thread.

          То, что иногда GUI элементы работают в других потоках - это скорее "счастливая случайность", которая рано или поздно перестанет работать. При наличии код-ревью такой код никогда не должен проходить в продакшн. Нет. Не так. Такой код никогда не должен проходить в продакшн ни при каких условиях.

            B
            • 19 ноября 2019 г. 6:24

            Вот плохо что у дронова не приводится таких выдержек..
            Ну все вроде правильно написанно, все виджеты в основном потоке у меня и создаются,
            я лишь передаю ссылку во вторичный поток что бы тот имел возможность изменять параметры виждета в основном потоке, а не создаю его (виджет) во вторичном потоке.

            The Qt GUI must run in this thread. All widgets and several related classes, for example QPixmap, don't work in secondary threads. (Все виджеты и несколько связанных классов, например, QPixmap, не работают во вторичных потоках.)
            Они деествительно не создаются даже, не то что не работают, я пробовал во вторичном потоке создать виджет с полями ввода и т.д., у меня ничего не получилось, мудохался день. И я сейчас с вашей помощью я знаю почему.
            Т.е. все виджеты создаются только в основном потоке, и если мне вдруг стукнуло в голову что то добавить, я должен перегрузить основной поток с изменениями,
            а не создавать вторичный и т.д.

            У меня текстовое поле textBrowser работает в основном потоке вместе с формой (с кучей кнопок и т.д.). В основной форме/потоке я создаю соединения с XML-PRC сервером, передаю/принимаю ему некоторую информацию, и все состояния и подтверждения передаются в textBrowser основного потока.

            Далее я создаю соединение/клиента UDP в основном потоке, и передаю его ссылку и ссылку на textBrowser с некоторыми другими переменными(флагами) во вторичный поток. И этот поток самостоятельно крутиться в режиме демона и передает состояние работы в текстовой форме по ссылке в textBrowser основного потока и я меняю по ссылке параметры выводимого текста. Т.е. textBrowser не работает во вторичном потоке, во вторичном потоке у него только ссылка на textBrowser. А еще у меня может работать третий поток пингующий IP + potr и то же принимающий ссылку на textBrowser основного потока и он туда шлет информацию о состоянии работы.

            Или я не так понял?

              Evgenii Legotckoi
              • 19 ноября 2019 г. 6:36

              Смотрите, думаю, что главная ваша ошибка состоит в том, что вы передаёте ссылку на textBrowser в другой поток. Когда вы выполняете передачу GUI объекта в другой поток, то вы рушите очередь событий, которая работает в основном потоке. Думаю, что поэтому GUI виджеты не следует передавать в другие потоки.

              Вы можете передать в другой поток какой-то класс, наследованный от QObject, например ваш XML-PRC сервер. А всю дальнейшую коммуникацию настроить через сигналы слоты. То есть, если что-то выполнилось в сервере, то с помощью сигнала высылаете информацию об этом в форму, где есть принимающий слот, и уже в этом слоте делайте что хотите с QLabel.

              То есть не держите ссылку в другом потоке. Тем более, что вы не пояснили, какой именно механизм вы используете для передачи ссылки в другой поток. Для меня это утверждение ничего не означает в данном случае, поскольку у Qt есть несколько механизмов создания потоков, а при передачи ссылки может измениться право владения объектом в потоке, что как раз и порушит работу GUI системы.

                B
                • 19 ноября 2019 г. 7:01

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

                Вообще этот textBrowser мне важен только для отладки наладчику, отключу пока сигналы не прикручу, включу лог для потока.

                Но конечно по сигналам если мне красиво надо разными цветами, шрифтами это еще тот велосипед наверное....

                  Evgenii Legotckoi
                  • 19 ноября 2019 г. 7:07

                  Мехонизм передачи простой, имя textBrowser в конструктор потока, все...

                  Код лучше покажите, а то на словах всё всегда хорошо

                  Но конечно по сигналам если мне красиво надо разными цветами, шрифтами это еще тот велосипед наверное....

                  А зачем тогда вообще Qt использовать, если не пользоваться сигналами? Кроссплатформенность только? Вы себя очень ограничите, если не пользоваться сигналами и слотами, это его главное преимущество.

                    B
                    • 19 ноября 2019 г. 7:17

                    Кода много лишнего, я лишнее выкину вечером

                      Evgenii Legotckoi
                      • 19 ноября 2019 г. 7:22

                      Ну тогда создайте, пожалуйста, потом новую тему на форуме с тем кодом, а то мы с вами в оффтоп ушли, здесь немного не о том было обсуждение.

                        Алексей Внуков
                        • 20 ноября 2019 г. 3:32
                        • (ред.)

                        пробежав глазами по топику, что хочу сказать(в питоне не сильно силен) - создание потока через метод run() давненько считается не корректным. более правильная работа с потоками считается когда создается обьект класа и переносится в другой поток
                        *.h

                        #include <QThread>
                        #include <worker.h> //клас который хотим поместить в поток
                        
                        class Base : public QObject
                        {
                            Q_OBJECT
                        public:
                            explicit Base(QObject *parent=nullptr);
                            ~Base();
                        public slots:
                        
                        private:
                            Worker *work;
                            QThread *thr_work;
                        };
                        
                        

                        *.cpp:

                        #include "base.h"
                        Base::Base(QObject *parent) : QObject(parent)
                        {
                            work=new Warker;
                            thr_work=new QThread(this);
                            work->moveToThread(thr_work);
                        
                            thr_work->start();
                            connect(work,&Worker::signal,this,&Base::slot);
                            connect(this,&Base::destroyed,thr_work,&QThread::quit);
                        }
                        

                        А в самом классе Worker делаете все что вам нужно, например по сигрналу старта потока

                          B
                          • 20 ноября 2019 г. 4:23

                          В питоне либо метод отдельно запускаем в потоке, либо наследуемся от Thread и там обязательно run переопределяем, да и в Java тоже самое run

                            Evgenii Legotckoi
                            • 20 ноября 2019 г. 4:29

                            В питоне тоже должен быть метод moveToThread у класса QThread, его и нужно использовать.

                            Java сюда не притягивайте за уши, это абсолютно другая платформа и другой язык.

                            Переопределение метода run у класса QThread при использовании Qt фреймворка уже давно считается некорректным, и сами разработчики Qt придерживаются этого же мнения и не рекомендуют наследование от QThread с переопределением метода run для обычного запуска какого-нибудь алгоритма в отдельном потоке.

                            Переопределение метода run должно выполняться в том случае, если вы хотите изменить поведение класса QThread, то есть создать свой кастомизированный класс потока, а для выполнения алгоритма в отдельном потоке следует использовать метод moveToThread. В противном случае всегда вылезают какие-нибудь грабли.

                              B
                              • 20 ноября 2019 г. 4:46

                              спасибо принял, поищу примеры

                                Комментарии

                                Только авторизованные пользователи могут публиковать комментарии.
                                Пожалуйста, авторизуйтесь или зарегистрируйтесь
                                B

                                C++ - Тест 002. Константы

                                • Результат:16баллов,
                                • Очки рейтинга-10
                                B

                                C++ - Тест 001. Первая программа и типы данных

                                • Результат:46баллов,
                                • Очки рейтинга-6
                                FL

                                C++ - Тест 006. Перечисления

                                • Результат:80баллов,
                                • Очки рейтинга4
                                Последние комментарии
                                k
                                kmssr8 февраля 2024 г. 18:43
                                Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
                                АК
                                Анатолий Кононенко5 февраля 2024 г. 1:50
                                Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
                                EVA
                                EVA25 декабря 2023 г. 10:30
                                Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
                                J
                                JonnyJo25 декабря 2023 г. 8:38
                                Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
                                G
                                Gvozdik18 декабря 2023 г. 21:01
                                Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
                                Сейчас обсуждают на форуме
                                P
                                Pisych27 февраля 2023 г. 4:04
                                Как получить в массив значения из связанной модели? Спасибо, разобрался:))
                                AC
                                Alexandru Codreanu19 января 2024 г. 11:57
                                QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…
                                BlinCT
                                BlinCT27 декабря 2023 г. 8:57
                                Растягивать Image на парент по высоте Ну и само собою дял включения scrollbar надо чтобы был Flickable. Так что выходит как то так Flickable{ id: root anchors.fill: parent clip: true property url linkFile p…
                                Дмитрий
                                Дмитрий10 января 2024 г. 4:18
                                Qt Creator загружает всю оперативную память Проблема решена. Удалось разобраться с помощью утилиты strace. Запустил ее: strace ./qtcreator Начал выводиться весь лог работы креатора. В один момент он начал считывать фай…
                                Evgenii Legotckoi
                                Evgenii Legotckoi12 декабря 2023 г. 6:48
                                Побуквенное сравнение двух строк Добрый день. Там случайно не высылается этот сигнал textChanged ещё и при форматировани текста? Если решиать в лоб, то можно просто отключать сигнал/слотовое соединение внутри слота и …

                                Следите за нами в социальных сетях