Один поток останавливает работу другого потока pyqt5
Помогите пожалуйста, я новичок в PyQt и понятия не имею в чем дело. Перечитал массу литературы и все равно не понимаю.
Пишу небольшую программку для собственных целей. Суть в том, что она тянет данные из MS SQL SERVER и закидывает их обратно.
В ней есть один очень долгоиграющий запрос, который я хочу поместить в отдельный поток, что бы он не блокировал работу всего приложения.
Сделал два класса в обоих есть функции, которые обращаются к SQL, тянут данные и возвращают результат.
Выглядит это примерно вот так:
class MyThread- выполняет две функции из основного классаclass MyThread4- как раз в нем и находится самый длинный запрос, который и должен быть в отдельном потоке, что бы не блокировать работу.
Проблема в том, что при запуске длинного потока MyThread4, он блокирует первый поток MyThread (приложение не зависает и продолжает работать).
Приложение не виснет, но MyThread не может продолжить работу, пока не завершится работа MyThread4.
import sys
from PyQt5.QtGui import QIcon
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QSize, Qt, QThread
from PyQt5.uic import loadUi
from PyQt5.QtWidgets import QDialog, QApplication
import pyodbc
from redminelib import Redmine
from dateutil.relativedelta import relativedelta
from io import BytesIO
from PyQt5.QtWidgets import (QApplication, QComboBox, QDialog,
QDialogButtonBox, QFormLayout, QGridLayout, QGroupBox, QHBoxLayout,
QLabel, QLineEdit, QMenu, QMenuBar, QPushButton, QSpinBox, QTextEdit,
QVBoxLayout)
class MyThread(QtCore.QObject):
mysignal1 = QtCore.pyqtSignal()
def __init__(self, parent=None):
QtCore.QObject.__init__(self, parent)
def run(self):
self.change_x()
def change_x(self):
while True:
print('start')
application.heavy_job()
time.sleep(6)
application.cpu_sql()
self.mysignal1.emit()
class MyThread4(QtCore.QObject):
mysignal = QtCore.pyqtSignal()
def __init__(self, parent=None):
QtCore.QObject.__init__(self, parent)
def second_work(self):
print('start signlal')
# Передача данных из потока через сигнал
application.index_frag_inf()
class mywindow(QtWidgets.QMainWindow):
def __init__(self):
super(mywindow, self).__init__()
self.ui = Ui_MainWindow()
self.server = None
self.password = None
self.username = None
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.worker1 = MyThread()
self.worker1.moveToThread(self.thread1)
self.ui.butt_conn.clicked.connect(self.worker1.change_x)
self.thread1.start()
self.worker = MyThread4()
self.worker.moveToThread(self.thread)
self.ui.startcheckindex.clicked.connect(self.worker.second_work)
self.thread.start()
self.ui.tableWidget.setColumnCount(6)
self.ui.tableWidget.setHorizontalHeaderLabels(('Fix', 'Index Type', 'TableName', 'IndexName', 'fragmentation %', 'SQLQuery'))
self.ui.tableWidget.setColumnWidth(0, 100)
self.ui.tableWidget.setColumnWidth(1, 100)
self.ui.tableWidget.setColumnWidth(2, 200)
self.ui.tableWidget.setColumnWidth(3, 400)
self.ui.tableWidget.setColumnWidth(4, 80)
self.ui.tableWidget.setColumnWidth(5, 500)
def index_frag_inf(self):
self.ui.tableWidget.clearContents()
self.cursor.execute("""
Здесь длинный запрос с очень долгим выполнением """)
self.ui.tableWidget.setRowCount(100)
tablerow = 0
for index in self.cursor.fetchall():
self.ui.tableWidget.setItem(tablerow, 0, QtWidgets.QTableWidgetItem(index[0]))
self.ui.tableWidget.setItem(tablerow, 1, QtWidgets.QTableWidgetItem(index[1]))
self.ui.tableWidget.setItem(tablerow, 2, QtWidgets.QTableWidgetItem(index[2]))
self.ui.tableWidget.setItem(tablerow, 3, QtWidgets.QTableWidgetItem(index[3]))
self.ui.tableWidget.setItem(tablerow, 4, QtWidgets.QTableWidgetItem(index[4]))
self.ui.tableWidget.setItem(tablerow, 5, QtWidgets.QTableWidgetItem(index[5]))
tablerow += 1
if __name__ == '__main__':
app = QtWidgets.QApplication([])
application = mywindow()
application.show()
application.setWindowTitle('LTMConsole')
sys.exit(app.exec())
Помогите пожалуйста! Не могу понять где и в чем именно проблема. Заранее благодарю и хорошего дня!
Ответы (3 шт):
Если таблица реально тяжелая, то самая первая ваша пробелема в
self.cursor.fetchall()
получение всех результатов процедура долгая. замените на итерации .cursor.fetch(1)
и второе (возможно более важное).
цикл обхода, если записей действительно много, заблокирует локальный эвентлуп. Попробуйте добавить в цикл qApp->processEvents() для обработки поступающих сигналов
Я попробовал что-то сделать для вас и немного прокомментировал код. Если что-то не понятно - спросите.
import sys
import random
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.Qt import *
#import pyodbc
#from redminelib import Redmine
#from dateutil.relativedelta import relativedelta
#from io import BytesIO
class MyThread(QtCore.QObject):
mysignal1 = QtCore.pyqtSignal(str, object) # !!!
def __init__(self, parent=None):
QtCore.QObject.__init__(self, parent)
self._stopped = True
def run(self):
self._stopped = False
print('start')
self.change_x()
def change_x(self):
while not self._stopped: # - True: + self._stopped
print('MyThread работает')
#- application.heavy_job() # -
#+ cursor.execute(""" ...""") # +++ добавите
#+ records = cursor.fetchall() # +++ добавите
records = 'Отработал запрос для heavy_job()' # !!! уберете
self.mysignal1.emit('heavy_job', records) # !!!
QtCore.QThread.msleep(1000 * 6) # time.sleep(6)
#- application.cpu_sql()
#+ cursor.execute(""" ...""") # +++ добавите
#+ records = cursor.fetchall() # +++ добавите
records = 'Отработал запрос для cpu_sql()' # !!! уберете
self.mysignal1.emit('cpu_sql', records) # !!!
QtCore.QThread.msleep(100)
def stop(self):
self._stopped = True
class MyThread4(QtCore.QObject):
mysignal = QtCore.pyqtSignal(object) # !!!
def __init__(self, parent=None):
QtCore.QObject.__init__(self, parent)
self._stopped = True
# def second_work(self):
def run(self):
print('start MyThread4')
count = 0
self._stopped = False
#+ ... тут ваш длительный запрос !!!
#+ cursor.execute(""" Здесь длинный запрос с очень долгим выполнением """)
#+ records = cursor.fetchone()
# vvv уберете - это имитация длительного запроса
records = []
for i in range(5000):
record = list(map(str, random.sample(range(999), 6)))
records.append(record)
QtCore.QThread.msleep(1000 * 3)
# ^^^
# Передача данных из потока через сигнал
self.mysignal.emit(records) # !!!
# application.index_frag_inf()
def stop(self):
self._stopped = True
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.centralwidget = QWidget()
self.setCentralWidget(self.centralwidget)
self.tableWidget = QTableWidget(0, 6)
self.textEdit = QTextEdit()
self.startcheckindex = QtWidgets.QPushButton('Start MyThread4')
self.startcheckindex.clicked.connect(self.handleButton)
self.butt_conn = QtWidgets.QPushButton('Start MyThread')
# self.butt_conn.clicked.connect(self.worker1.change_x)
self.butt_conn.clicked.connect(self.butt_connButton)
self.thread1 = QtCore.QThread(self) # +++
self.worker1 = MyThread()
self.worker1.moveToThread(self.thread1)
self.worker1.mysignal1.connect(self.worker1_mysignal1) # !!!
# self.butt_conn.clicked.connect(self.worker1.change_x)
# self.thread1.start()
self.thread1.started.connect(self.worker1.run) # !!!
self.thread = QtCore.QThread(self) # +++
self.worker = MyThread4()
self.worker.moveToThread(self.thread)
self.worker.mysignal.connect(self.index_frag_inf) # !!!
# self.startcheckindex.clicked.connect(self.worker.second_work)
# self.thread.start()
self.thread.started.connect(self.worker.run) # !!!
# self.tableWidget.setColumnCount(6)
self.tableWidget.setHorizontalHeaderLabels(
('Fix',
'Index Type',
'TableName',
'IndexName',
'fragmentation %',
'SQLQuery'
)
)
self.tableWidget.setColumnWidth(0, 100)
self.tableWidget.setColumnWidth(1, 100)
self.tableWidget.setColumnWidth(2, 200)
self.tableWidget.setColumnWidth(3, 300)
self.tableWidget.setColumnWidth(4, 80)
self.tableWidget.setColumnWidth(5, 300)
layout = QGridLayout(self.centralwidget)
layout.addWidget(self.tableWidget, 0, 0, 1, 1)
layout.addWidget(self.textEdit, 0, 1, 1, 1)
layout.addWidget(self.startcheckindex, 1, 0)
layout.addWidget(self.butt_conn, 1, 1)
def handleButton(self):
if self.thread.isRunning():
self.worker.stop()
else:
self.startcheckindex.setText('Stop MyThread4')
self.thread.start()
def butt_connButton(self):
if self.thread1.isRunning():
self.worker1.stop()
self.textEdit.append('Stop MyThread')
self.butt_conn.setText('Start MyThread')
self.thread1.quit()
else:
self.textEdit.append('Start MyThread')
self.butt_conn.setText('Stop MyThread')
self.thread1.start()
def worker1_mysignal1(self, text, records): # <----
if text == 'heavy_job':
# self.ui.heavyjob.setText(str(records))
self.textEdit.append(str(records))
elif text == 'cpu_sql':
# self.ui.cpupercent.setText(str(records))
self.textEdit.append(str(records))
def index_frag_inf(self, records): # <----
self.startcheckindex.setText('Start MyThread4')
self.thread.quit()
# self.tableWidget.clearContents()
model = self.tableWidget.model()
model.removeRows(0, model.rowCount())
# self.ui.tableWidget.setRowCount(100) void QTableWidget::insertRow(int row)
# tablerow = 0
for index, record in enumerate(records):
self.tableWidget.insertRow(index)
self.tableWidget.setItem(index, 0, QtWidgets.QTableWidgetItem(record[0]))
self.tableWidget.setItem(index, 1, QtWidgets.QTableWidgetItem(record[1]))
self.tableWidget.setItem(index, 2, QtWidgets.QTableWidgetItem(record[2]))
self.tableWidget.setItem(index, 3, QtWidgets.QTableWidgetItem(record[3]))
self.tableWidget.setItem(index, 4, QtWidgets.QTableWidgetItem(record[4]))
self.tableWidget.setItem(index, 5, QtWidgets.QTableWidgetItem(record[5]))
def closeEvent(self, event):
self.worker.stop()
self.worker1.stop()
self.worker.deleteLater()
self.worker1.deleteLater()
splash = QtWidgets.QSplashScreen()
splash.setPixmap(QtGui.QPixmap('images/splash.jpg'))
splash.show()
splash.showMessage(
'<h1 style="color:white;"><br><br>Подождите пожалуйста приложение закрывается</h1>',
QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter, QtCore.Qt.white)
QtCore.QThread.msleep(3000)
splash.showMessage(
'<h1 style="color:white;"><br><br>Спасибо, что посетили нас, всего хорошего.</h1>',
QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter, QtCore.Qt.white)
QtCore.QThread.msleep(3000)
self.thread.quit()
self.thread1.quit()
if __name__ == '__main__':
app = QtWidgets.QApplication([])
w = MyWindow()
w.resize(1100, 300)
w.show()
w.setWindowTitle('LTMConsole')
sys.exit(app.exec())
Если кто вдруг столкнется с такой проблемой как у меня. ВСЕГДА используйте отдельный курсор для каждого подключения, то есть:
def heavy_job(self): -- функция с каким-то запросом
self.conn = conn = pyodbc.connect(
'DRIVER={ODBC Driver 17 for SQL Server};SERVER=' + self.server + ';DATABASE=' + self.database + ';UID='
+ self.username + ';PWD=' + self.password, autocommit=True) # запускает соединение с SQL-сервером
cursor_job = conn.cursor() -- именнь здесь, вы должны задавать индивидуальное название Вашему курсору.
cursor_job.execute("""Тут будет какой-то запрос""")
for heavyjob in cursor_job.fetchall(): и тут уже выводите всю нужную Вам инфу.
Теперь, когда Вы будете использовать отдельные потоки, то питону и sql не надо будет драться за один единственный курсор, что бы выполнить все без ошибок, так как таких функицй как heavy_job у Вас может быть 1000 и 1. И в каждом должно быть индивидуальное название.
Более подробно Вы можете ознакомиться в статье, что я нашел
Надеюсь, что я правильно донес мысль и не ввел в заблуждение, но данный момент мне действительно помог решить проблему.

