I updated somethings like calling the on_event method from the thread class and now i have this code :
# -*- coding: utf-8 -*-
from PyQt4 import QtCore, QtGui
import sys, pyodbc, serial
import os
import time
import thread
from PyQt4.QtCore import pyqtSignal
from PyQt4.QtCore import QObject, pyqtSignal
#Variables
Code_Zone = "d"
class MainWindow(QtGui.QWidget):
def __init__(self, main):
super(MainWindow, self).__init__()
self.main = main
self.grid = QtGui.QGridLayout(self)
self.welcome = QtGui.QLabel("Bienvenue sur www.developpez.net", self)
self.grid.addWidget(self.welcome, 2, 2, 1, 5)
class AccessWindow(QtGui.QWidget):
def __init__(self):
super(AccessWindow, self).__init__()
self.setMinimumSize(150, 50)
self.grid = QtGui.QGridLayout(self)
self.label = QtGui.QLabel(self)
self.grid.addWidget(self.label, 1, 1, 1, 1)
class Main(object):
global EPC_Code
def __init__(self):
self.accueil = MainWindow(self)
self.accueil.show()
self.access = AccessWindow()
def on_event(self, data):
def refresh():
self.toggle_widget(False)
#c = AThread()
c.run()
#self.wait()
# vérification des données
EPC_Code = data
cnxn = """DRIVER={SQL Server};SERVER=Aziz-PC\SQLEXPRESS;PORT=1433;UID=poleindus;PWD=poleindus;DATABASE=indusdb"""
db_connection = pyodbc.connect(cnxn)
db_cursor = db_connection.cursor()
print ('Connected TO DB & READY')
sql_command = "EXECUTE [Get_Access_RFID] #Code_RFID = '"+EPC_Code+"', #Code_Zone = '"+Code_Zone+"'"
db_cursor.execute("EXECUTE [Get_Access_RFID] #Code_RFID = '"+EPC_Code+"', #Code_Zone = '"+Code_Zone+"'")
rows = db_cursor.fetchone()
result= str(rows[0])
print ("result = " + str(result))
if result == "True":
# si OK
self.access.label.setText('ACCESS GRANTED')
else:
# si pas OK
self.access.label.setText('ACCESS DENIED')
self.toggle_widget(True)
QtCore.QTimer.singleShot(2000, refresh)
def toggle_widget(self, b):
self.accueil.setVisible(not b)
self.access.setVisible(b)
class AThread(QtCore.QThread):
rfid_event = pyqtSignal(str, name='rfid_event')
def run(self):
while 1:
ser = serial.Serial(port='COM6', baudrate=115200)
a = ser.read(19).encode('hex')
ser.close()
if len(a) == 38:
EPC_Code = a[14:]
print ('EPC is : ' + EPC_Code)
self.rfid_event.emit(EPC_Code)
if __name__=='__main__':
#cnxn = """DRIVER={SQL Server};SERVER=Aziz-PC\SQLEXPRESS;PORT=1433;UID=poleindus;PWD=poleindus;DATABASE=indusdb"""
#db_connection = pyodbc.connect(cnxn)
#db_cursor = db_connection.cursor()
#print ('Connected TO DB & READY')
app = QtGui.QApplication(sys.argv)
main = Main()
thread = AThread()
thread.rfid_event.connect(Main().on_event)
thread.start()
#main = Main()
sys.exit(app.exec_())
I have already added this line at the top of the code and now, i have another error: The first argument to execute must be a string or unicode query.when i checked the value of the sql command, i find it equals to: PyQt4.QtCore.QString(u"EXECUTE [Get_Access_RFID] #Code_RFID = 'e20010712904019411709bdd', #Code_Zone = 'd'")
You create a new Main in AThread. That means you now have two of them (and worse, you create one every time you process an RFID event).
Also, you're not allowed to call methods from Main from inside a thread. Imagine what would happen if the thread would call them again before the first call would return: Data would be corrupted.
Instead, you must declare a signal in AThread and call emit() to send it. Your Main from the last block of code can then connect to this signal.
class AThread(QtCore.QThread):
rfid_event = pyqtSignal(str, name='rfid_event')
def run(self):
while 1:
ser = serial.Serial(port='COM6', baudrate=115200)
a = ser.read(19).encode('hex')
ser.close()
if len(a) == 38:
EPC_Code = a[14:]
print ('EPC is : ' + EPC_Code)
self.rfid_event.emit(EPC_Code)
and later in the __main__ code:
main = Main()
thread = AThread()
thread.rfid_event.coonect(Main.on_event)
thread.start()
#main = Main()
sys.exit(app.exec_())
Also note that thread.finished.connect(app.exit) won't work: The thread contains an endless loop and never finishes. Instead, you should terminate the thread when the main window is closed.
I also fixed access to the serial port; you must close it every time.
Related
I'm trying to run a few slow processes but, I need to keep updated the QDialog to show the progress (maybe I put a progress bar too).
So I decide to use QThread, but on the first try, it doesn't work as I expected.
In my example code:
1- I'm using a simple ping to my default gateway
2- I'm pinging to my dns resolver
As you can see on imagem below, the information is showed according the thread is finalizing, but it is a mess to me.
Is possible to respect the threads order to show the informations?
Thanks.
Follow my example code:
# -*- coding: utf8 -*-
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from ping3 import ping, verbose_ping
import socket
import dns.resolver
class ExternalTests(QThread):
data_collected = pyqtSignal(object)
def __init__(self, title, arg=None):
QThread.__init__(self)
self.title = title
self.arg = arg
def run(self):
resp = ping(self.arg)
self.data_collected.emit('%s: %s' % (self.title, resp))
class MainMenu(QMenu):
def __init__(self, parent=None):
QMenu.__init__(self)
self.setStyleSheet("background-color: #3a80cd; color: rgb(255,255,255); selection-color: white; selection-background-color: #489de4;")
# Diagnostics
self.check_internet = QAction("Diagnosys")
self.check_internet.setIcon(QIcon(QPixmap("..\\img\\lupa.png")))
self.check_internet.triggered.connect(self.diagnosticNetwork)
self.addAction(self.check_internet)
self.addSeparator()
# To quit the app
self.quit = QAction("Quit")
self.quit.triggered.connect(app.quit)
self.addAction(self.quit)
def diagnosticNetwork(self):
self.check_internet_dialog = QDialog()
self.check_internet_dialog.setWindowTitle("Check external connections")
self.check_internet_dialog.setWindowModality(Qt.ApplicationModal)
self.check_internet_dialog.setGeometry(150, 100, 700, 500)
# text box
self.textbox = QTextBrowser(self.check_internet_dialog)
self.textbox.move(20, 20)
self.textbox.resize(660,400)
self.textbox.setFont(QFont("Courier New", 12))
self.textbox.setStyleSheet("background-color: black;")
#button copy
btn_copy = QPushButton("Copy", self.check_internet_dialog)
btn_copy.setIcon(QIcon(QPixmap("..\\img\\copy.png")))
btn_copy.move(520,450)
btn_copy.clicked.connect(self.dialogClickCopy)
#button close
btn_copy = QPushButton("Close", self.check_internet_dialog)
btn_copy.setIcon(QIcon(QPixmap("..\\img\\close.png")))
btn_copy.move(605,450)
btn_copy.clicked.connect(self.dialogClickClose)
# tests
self.textbox.setTextColor(QColor("white"))
self.textbox.append("Diagnosys")
self.textbox.append("--------------------------------------------------")
self.textbox.setTextColor(QColor("cyan"))
self.threads = []
#QCoreApplication.processEvents()
''' ping default gateway '''
ping_default_gw = ExternalTests("default gatewat is reacheble", "192.168.0.1")
ping_default_gw.data_collected.connect(self.onDataReady)
self.threads.append(ping_default_gw)
ping_default_gw.start()
''' ping dns resolver '''
ping_dns_resolvers = dns.resolver.Resolver().nameservers
for dns_resolver in ping_dns_resolvers:
ping_dns_resolver = ExternalTests("dns resolver is reacheble %s" % dns_resolver, dns_resolver)
ping_dns_resolver.data_collected.connect(self.onDataReady)
self.threads.append(ping_dns_resolver)
ping_dns_resolver.start()
self.check_internet_dialog.exec_()
def onDataReady(self, data):
print(data)
if data:
self.textbox.append(data)
else:
self.textbox.append("error")
def dialogClickCopy(self):
pass
def dialogClickClose(self):
self.check_internet_dialog.close()
class SystemTrayIcon(QSystemTrayIcon):
def __init__(self, menu, parent=None):
QSystemTrayIcon.__init__(self)
self.setIcon(QIcon("..\\img\\icon.png"))
self.setVisible(True)
self.setContextMenu(menu)
if __name__ == "__main__":
import sys
app = QApplication([])
app.setQuitOnLastWindowClosed(False)
app.setApplicationName('pkimonitor')
app.setApplicationVersion('0.1')
app.setWindowIcon(QIcon("..\\img\\icon.png"))
menu = MainMenu()
widget = QWidget()
trayIcon = SystemTrayIcon(menu, widget)
trayIcon.show()
sys.exit(app.exec_())
I tried to create a scheme to organize by "run position" and it works. Follow my code.
In 'diagnosticNetwork':
self.data_list = []
self.data_hold = []
In 'onDataReady':
if len(self.data_list) > 0:
if self.data_list[-1][0] + 1 == data[0]:
self.data_list.append(data)
if data[2]:
self.textbox.append("%s: %s" % (data[1], 'OK'))
else:
self.textbox.append("%s: %s" % (data[1], 'NOK'))
elif self.data_list[-1][0] < data[0]:
self.data_hold.append(data)
else:
self.data_list.append(data)
if data[2]:
self.textbox.append("%s: %s" % (data[1], 'OK'))
else:
self.textbox.append("%s: %s" % (data[1], 'NOK'))
if len(self.data_hold) > 0:
hold_sorted = self.data_hold[:]
hold_sorted.sort()
for line_hold in hold_sorted:
if self.data_list[-1][0] + 1 == line_hold[0]:
if line_hold[2]:
self.textbox.append("%s: %s" % (line_hold[1], 'OK'))
else:
self.textbox.append("%s: %s" % (line_hold[1], 'NOK'))
self.data_list.append(line_hold)
del self.data_hold[0]
I worked with two lists, data_list and data_hold. The results that I received from the threads, I filled out the main list according to the order that I pre-established, if the result that entered was not the next one in the sequence, it goes to data_hold, then scan this list to fill the rest of my textbox.
Thank you for all help!
In my PySide2 application there is a downloadCodesButton button clicking on which should execute a function in a separate QThread thread and display a value in a countCodesLabel label. But after clicking on the button nothing happens. Why?
My code:
import random
import colorama
import time
import os
import sys
import design
import tempfile
from PySide2 import QtWidgets, QtCore, QtGui
class Threads(QtCore.QObject):
running = False
downloadCodesReadySignal = QtCore.Signal(str, object)
myvar = None
def __init__(self, myvar, parent=None):
self.myvar = myvar
super().__init__()
def downloadCodes(self):
countCodes = 0
with open(self.myvar, 'r') as f:
line = f.readline()
while line:
codes.append(line)
countCodes = countCodes + 1
line = f.readline()
self.downloadCodesReadySignal.emit(countCodes)
class WarGenApp(QtWidgets.QMainWindow, design.Ui_Form):
def __init__(self):
super().__init__()
self.setupUi(self)
self.downloadCodesButton.clicked.connect(self.downloadCodes)
#QtCore.Slot(str, object)
def downloadCodesReady(self, countCodes):
self.countCodesLabel.setText(str(countCodes))
def downloadCodes(self):
fname = QtWidgets.QFileDialog.getOpenFileName(self, 'Открыть файл', '', 'Text files (*.txt)')[0]
self.thread = QtCore.QThread()
self.Threads = Threads(myvar=fname)
self.Threads.moveToThread(self.thread)
self.Threads.downloadCodesReadySignal.connect(self.downloadCodesReady)
self.thread.started.connect(self.Threads.downloadCodes)
self.thread.start()
def main():
app = QtWidgets.QApplication(sys.argv)
form = WarGenApp()
form.show()
app.exec_()
if __name__ == '__main__':
main()
What am I missing?
Your problem is at passing information into the slot. QtCore.Signal(str, object) expects a string and a object, but you only pass an object. Just change the #QtCore.Slot(str,object) to #QtCore.Slot(object) and adjust the signal calls accordingly and everything should work. Also, it might be a good idea to terminate the thread when it is done.
#QtCore.Slot(object)
def downloadCodesReady(self, countCodes):
self.countCodesLabel.setText(str(countCodes))
self.thread.terminate()
Finally, you are appending some undefined codes list in the downloadCodes function.
def downloadCodes(self):
countCodes = 0
with open(self.myvar, 'r') as f:
line = f.readline()
while line:
# codes.append(line) This one crashes the code
countCodes = countCodes + 1
line = f.readline()
self.downloadCodesReadySignal.emit(countCodes)
Eventhough the thread crashes, it does not automatically terminate itself. It just seems, that nothing happens. Might be a good idea to add some exit condition in case the code won't run to safely exit and terminate the thread.
I have a websocket server running in python, and for every new connection a new thread will be created and the requests will be served.
In the main-thread [Gui-thread],i am initialing QApplication([]). the use case is, when i process the request i wanted to wait and get text response from the user through QInputDialog.
when ever i run it, there is a event-loop running but is not showing the gui. because all the gui elements can be displayed from Gui-thread itself.
I have tried various approaches using QSignals/slots and Pypubsub but unable to achieve what is required. please do suggest some idea to get the use-case done. a pseudo-code is well appreciated.
Below mentioned code are some examples i tried. i am using thread in below examples because, as i mentioned each request from a connection is executed with the thread assigned to the connection. and the text from QInputDialog is required by the thread.
thanks in advance.
below is the websockets server code which serves the request calling server_extentions function, i have to show QInputDialog everytime i get a incoming request.
import websockets
import asyncio
from PyQt5.QtWidgets import QInputDialog, QApplication
app = QApplication([])
async def server_extentions(websocket, path):
try:
while(True):
request = await websocket.recv()
# this is where i need to show input dialog.
text, ok = QInputDialog.getText(None, "Incoming message", request)
if ok:
response = text
else:
response = "NO REPLY"
await websocket.send(response)
except websockets.ConnectionClosed as exp:
print("connection closed.")
start_server = websockets.serve(server_extentions, '127.0.0.1', 5588)
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(start_server)
loop.run_forever()
finally:
loop.run_until_complete(loop.shutdown_asyncgens())
loop.close()
----edit-----
Below is some general idea, i tried using pypubsub.
import threading
import pubsub.pub
from PyQt5.QtWidgets import QInputDialog, QApplication
class MainThread:
def __init__(self):
self.app = QApplication([])
pubsub.pub.subscribe(self.pub_callback, "lala")
def pub_callback(self):
print("this is Main thread's pub callback.")
QInputDialog.getText(None, "main-thread", "lala call back : ")
def start_thread(self):
self.th = threading.Thread(target=self.thread_proc)
self.th.start()
def thread_proc(self):
pubsub.pub.sendMessage("lala")
m = MainThread()
m.start_thread()
-----edit 2 -------
below is something i tried with QSignal. [check the comment in the code, How to call a function with Mainthread].
import threading
from PyQt5.QtWidgets import QInputDialog, QApplication
from PyQt5.QtCore import pyqtSignal, QObject, QThread
class TextDialog(QObject):
sig = pyqtSignal(str)
def __init__(self):
QObject.__init__(self)
def get_text(self):
print("class Thread2, showing QInputDialog.")
text, ok = QInputDialog.getText(None, "Lala", "give me some text : ")
if ok:
self.sig.emit(text)
return
self.sig.emit("NO TEXT")
return
class Thread1:
def thread_proc(self):
td = TextDialog()
td.sig.connect(self.get_text_callback)
td.moveToThread(m.main_thread)
# here i dont understand how to invoke MainThread's show_dialog with main thread. [GUI Thread]
#m.show_dialog(td)
def get_text_callback(self, txt):
print("this is get_text_callback, input : " + str(txt))
class MainThread:
def __init__(self):
self.app = QApplication([])
self.main_thread = QThread.currentThread()
def main_proc(self):
th1 = Thread1()
th = threading.Thread(target=th1.thread_proc)
th.start()
def show_dialog(self, text_dialog: TextDialog):
print("got a call to MainThread's show_dialog.")
text_dialog.get_text()
m = MainThread()
m.main_proc()
exit()
For this type of applications it is better to implement the worker-thread approach. This approach the main idea is to implement QObjects, move them to a new thread and invoke the slots asynchronously (through QEvents, pyqtSignals, QTimer.singleShot(...), QMetaObject::invokeMethod(...), etc) so that the tasks are executed in the thread that Live the QObject.
import threading
from functools import partial
from PyQt5 import QtCore, QtWidgets
class TextDialog(QtCore.QObject):
sig = QtCore.pyqtSignal(str)
#QtCore.pyqtSlot()
def get_text(self):
print("class Thread2, showing QInputDialog.")
text, ok = QtWidgets.QInputDialog.getText(
None, "Lala", "give me some text : "
)
if ok:
self.sig.emit(text)
return
self.sig.emit("NO TEXT")
return
class Worker1(QtCore.QObject):
#QtCore.pyqtSlot(QtCore.QObject)
def thread_proc(self, manager):
print(
"current: {}- main: {}".format(
threading.current_thread(), threading.main_thread()
)
)
manager.td.sig.connect(self.get_text_callback)
QtCore.QTimer.singleShot(0, manager.show_dialog)
#QtCore.pyqtSlot(str)
def get_text_callback(self, txt):
print(
"current: {}- main: {}".format(
threading.current_thread(), threading.main_thread()
)
)
print("this is get_text_callback, input : %s" % (txt,))
class Manager(QtCore.QObject):
def __init__(self, parent=None):
super().__init__(parent)
self.td = TextDialog()
#QtCore.pyqtSlot()
def show_dialog(self):
print("got a call to MainThread's show_dialog.")
self.td.get_text()
class Application:
def __init__(self):
print(
"current: {}- main: {}".format(
threading.current_thread(), threading.main_thread()
)
)
self.app = QtWidgets.QApplication([])
# By default if after opening a window all the windows are closed
# the application will be terminated, in this case after opening
# and closing the QInputDialog the application will be closed avoiding
# that it will be noticed that get_text_callback is called,
# to avoid the above it is deactivated behavior.
self.app.setQuitOnLastWindowClosed(False)
self.manager = Manager()
def main_proc(self):
#
self.thread = QtCore.QThread()
self.thread.start()
self.worker = Worker1()
# move the worker so that it lives in the thread that handles the QThread
self.worker.moveToThread(self.thread)
# calling function asynchronously
# will cause the function to run on the worker's thread
QtCore.QTimer.singleShot(
0, partial(self.worker.thread_proc, self.manager)
)
def run(self):
return self.app.exec_()
if __name__ == "__main__":
import sys
m = Application()
m.main_proc()
ret = m.run()
sys.exit(ret)
Looking to thread my app. Below is a portion of the code. From my understanding, I need to emit a signal from my thread class, and in my Main class, I start the thread. I just want to be able to move the window around and not lose functionality while the final_button action is taking place. Any help would be appreciated.
Edit: based on eyllanesc's feedback, changed my code to be an if statement rather than a while True statement. The problem still persists - once I press the final button, the app loses control. The window can't be moved and the title bar shows Not Responding until the final button actions finish. I'm wondering if I can adjust my code below to fix the issue. Thanks.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog, QMessageBox
from PyQt5.QtCore import pyqtSignal
from config_assign_ui import Ui_ConfigAssign
import pyodbc
import pandas as pd
class Main(QtWidgets.QMainWindow, Ui_ConfigAssign):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.setupUi(self)
self.thread = Thread()
self.thread.start()
self.thread.final_button.connect(self.final_button)
self.combo_list()
self.ImportMapButton.clicked.connect(self.import_map)
self.ExceptButton.clicked.connect(self.except_file)
self.FinalButton.clicked.connect(self.final_button)
def combo_list(self):
# do a SQL query to show a list of customer options
def import_map(self, df_map):
name = QFileDialog.getOpenFileName(self, "Open Map...", "T:/Drive",
"Worksheets (*.xlsx;*.xlsm;*.xlsb;*.xls)")
file = str(name[0])
if file != '':
while True:
try:
xl = pd.ExcelFile(file)
self.df_map = xl.parse('Sheet1')
rows = self.df_map.shape[0]
rows = "{:,}".format(rows)
self.labelMapCount.setText(rows)
except:
mb = QMessageBox()
mb.setWindowTitle('Problem with File')
mb.setText("Please try again")
mb.setStyleSheet("QLabel{min-width:180 px}")
mb.exec_()
break
# check columns of file
cols = ['CategoryId','CategoryName','SubcategoryId','SubcategoryName','MakeId','MakeName']
cols_file = list(self.df_map)
if cols_file != cols:
mb = QMessageBox()
mb.setWindowTitle('Column Error')
mb.setText('Columns names do not match template')
mb.setStyleSheet('QLabel{min-width:220 px}')
mb.exec_()
break
else:
break
def except_file(self, df_except):
if self.ExceptCheckBox.isChecked():
# do some processing of an exception file that the user can import
def final_button(self):
# check if df_map exists
try:
print(self.df_map.head())
map_avail = 1
except AttributeError:
mb = QMessageBox()
mb.setWindowTitle('Map Missing')
mb.setText('Please import map first')
mb.setStyleSheet("QLabel{min-width:200 px}")
mb.exec_()
map_avail = 0
if map_avail == 1:
#perform a bunch of actions
class Thread(QtCore.QThread):
final_button = pyqtSignal()
def __init__(self):
Thread.__init__(self)
def run(self):
while True:
time.sleep(2)
self.final_button.emit()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Main()
window.show()
sys.exit(app.exec_())
Background :
I have a script that allows me to make spatial queries on a PostgreSQL database via an API coming from a private editor (I can't directly query the database). This API is working with python 3.2. To summarize very quickly, this script is used to download elements of this database in the desired geographical footprint. Depending of the zone, you can obtain between 1 to over 100 elements, each of them having very different sizes (from Ko to Go).
The main window let you to set all options and then start the global process. When launched a console window appears letting you see what’s going on. Once an item has been downloaded a short “report” is displayed on the console. Currently everything is done sequentially one element at a time. As you can imagine, if this element is quite large, the console freezes while waiting for the end of the download process.
Code:
I'm not going to post the full script here, but through a very simple script I will try to show the main problem I'm trying to solve (i.e. avoid locking the user interface / have some sort of real-time output on what's going on).
So, in order to avoid these freezing problems, the use of threads seemed to me to be the best solution. To simulate the download process (see previous chapter) I used the url.request urlretrieve method with multiple urls (pointing to files of different sizes).
import os
import sys
import time
import urllib.request
from PyQt4 import QtCore, QtGui
url_1m = 'http://ipv4.sbg.proof.ovh.net/files/1Mio.dat'
url_10m = 'http://ipv4.sbg.proof.ovh.net/files/10Mio.dat'
url_100m = 'http://ipv4.sbg.proof.ovh.net/files/100Mio.dat'
url_1g = 'http://ipv4.sbg.proof.ovh.net/files/1Gio.dat'
url_10g = 'http://ipv4.sbg.proof.ovh.net/files/10Gio.dat'
urls = (url_1m, url_10m, url_100m, url_1g, url_10g)
# ---------------------------------------------------------------------------------
class DownloadWorkerSignals(QtCore.QObject):
"""
Defines the signals available from a running download worker thread.
"""
finished = QtCore.pyqtSignal(str)
# ---------------------------------------------------------------------------------
class DownloadWorker(QtCore.QRunnable):
"""
Worker thread
"""
def __init__(self, url, filepath, filename, index):
super(DownloadWorker, self).__init__()
self.url = url
self.file_path = filepath
self.filename = filename
self.index = index
self.signals = DownloadWorkerSignals()
#QtCore.pyqtSlot(str)
def run(self):
t = time.time()
message = 'Thread %d started\n' % self.index
try:
# The urlretrieve method will copy a network object to a local file
urllib.request.urlretrieve(url=self.url,
filename=os.path.join(self.file_path,
self.filename))
except IOError as error:
message += str(error) + '\n'
finally:
message += 'Thread %d ended %.2f s\n' % (self.index, time.time() - t)
self.signals.finished.emit(message) # Done
# ---------------------------------------------------------------------------------
class Main(QtGui.QMainWindow):
"""
Main window
"""
def __init__(self):
super(self.__class__, self).__init__()
self.resize(400, 200)
self.setWindowTitle("Main")
self.setWindowModality(QtCore.Qt.ApplicationModal)
self.centralwidget = QtGui.QWidget(self)
self.setCentralWidget(self.centralwidget)
# Ok / Close
# -------------------------------------------------------------------------
self.buttonBox = QtGui.QDialogButtonBox(self.centralwidget)
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel |
QtGui.QDialogButtonBox.Ok)
self.buttonBox.setGeometry(QtCore.QRect(10, 160, 380, 20))
# Connect definition
# -------------------------------------------------------------------------
self.connect(self.buttonBox,
QtCore.SIGNAL('accepted()'),
self.button_ok_clicked)
self.connect(self.buttonBox,
QtCore.SIGNAL('rejected()'),
self.button_cancel_clicked)
# Connect functions
# -----------------------------------------------------------------------------
def button_cancel_clicked(self):
self.close()
def button_ok_clicked(self):
# Launch console
console = Console(parent=self)
console.exec_()
# ---------------------------------------------------------------------------------------------------------------
class Console(QtGui.QDialog):
"""
Console window
"""
def __init__(self, parent):
super(self.__class__, self).__init__()
self.parent = parent
self.resize(400, 200)
self.setWindowTitle("Console")
self.setModal(True)
self.verticalLayout = QtGui.QVBoxLayout(self)
# Text edit
# -------------------------------------------------------------------------
self.text_edit = QtGui.QPlainTextEdit(self)
self.text_edit.setReadOnly(True)
self.text_edit_cursor = QtGui.QTextCursor(self.text_edit.document())
self.verticalLayout.addWidget(self.text_edit)
# Ok / Close
# -------------------------------------------------------------------------
self.button_box = QtGui.QDialogButtonBox(self)
self.button_box.setStandardButtons(QtGui.QDialogButtonBox.Close)
self.verticalLayout.addWidget(self.button_box)
# Connect definition
# -------------------------------------------------------------------------
self.connect(self.button_box.button(QtGui.QDialogButtonBox.Close),
QtCore.SIGNAL('clicked()'),
self.button_cancel_clicked)
# Post initialization
# -------------------------------------------------------------------------
self.threadpool = QtCore.QThreadPool()
self.threadpool.setMaxThreadCount(2)
for index, url in enumerate(urls):
worker = DownloadWorker(url=url,
filepath='C:\\Users\\philippe\\Downloads',
filename='url_%d.txt' % index,
index=index)
worker.signals.finished.connect(self.write_message)
self.threadpool.start(worker)
'''
I have to wait for the end of the thread pool to make a post-processing.
If I use the waitForDone I don't see my console until the all work is done
'''
# self.threadpool.waitForDone()
# self.write_stram('Thread pool finished')
# Connect functions
# -----------------------------------------------------------------------------
def button_cancel_clicked(self):
if self.threadpool.activeThreadCount() != 0:
pass # How to interrupt the threadpool ?
self.close()
#QtCore.pyqtSlot(str)
def write_message(self, text):
self.text_edit.insertPlainText(text)
cursor = self.text_edit.textCursor()
self.text_edit.setTextCursor(cursor)
# ---------------------------------------------------------------------------------
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Main()
window.show()
app.exec_()
Questions:
Everything seems to work as expected but I encounter two difficulties:
At the end of the thread pool process I have to make some
post-processing. If I use the waitForDone method I don't see my
console until the all work is done and it’s not the type of behavior
wanted.
If the Cancel Button in the Console is clicked, I need to interrupt
the threadpool and I don’t know how to manage that.
I had another look at this problem (based largely on this : how-do-i-maintain-a-resposive-gui-using-qthread-with-pyqgis).
So I replaced the previous tandem QThreadPool/QRunnable, by Queue/QThread. The code below gives an overview.
import os
import sys
import time
import urllib.request
import queue
from PyQt4 import QtCore, QtGui
url_1m = 'http://ipv4.sbg.proof.ovh.net/files/1Mio.dat'
url_10m = 'http://ipv4.sbg.proof.ovh.net/files/10Mio.dat'
url_100m = 'http://ipv4.sbg.proof.ovh.net/files/100Mio.dat'
url_1g = 'http://ipv4.sbg.proof.ovh.net/files/1Gio.dat'
url_10g = 'http://ipv4.sbg.proof.ovh.net/files/10Gio.dat'
urls = (url_1m, url_10m, url_100m, url_1g, url_10g)
# ---------------------------------------------------------------------------------
class WorkerThread(QtCore.QThread):
"""
Worker thread
"""
def __init__(self, parent_thread):
QtCore.QThread.__init__(self, parent_thread)
def run(self):
self.running = True
success = self.do_work()
self.emit(QtCore.SIGNAL('jobFinished(PyQt_PyObject)'), success)
def stop(self):
self.running = False
pass
def do_work(self):
return True
def clean_up(self):
pass
# ---------------------------------------------------------------------------------
class LongRunningTask(WorkerThread):
def __init__(self, parent_thread, url, filepath, filename, index):
WorkerThread.__init__(self, parent_thread)
self.url = url
self.filepath = filepath
self.filename = filename
self.index = index
def do_work(self):
t = time.time()
self.emit(QtCore.SIGNAL('threadText(PyQt_PyObject)'), 'Thread %d started\n' % self.index)
try:
# The urlretrieve method will copy a network object to a local file
urllib.request.urlretrieve(url=self.url,
filename=os.path.join(self.filepath,
self.filename))
except IOError as error:
self.emit(QtCore.SIGNAL('threadText(PyQt_PyObject)'),
'Thread %d error - ' % self.index + str(error) + '\n')
finally:
self.emit(QtCore.SIGNAL('threadText(PyQt_PyObject)'),
'Thread %d ended %.2f s\n' % (self.index, time.time() - t))
return True
# ---------------------------------------------------------------------------------
class Console(QtGui.QDialog):
"""
Console window
"""
def __init__(self):
super(self.__class__, self).__init__()
self.resize(400, 200)
self.setWindowTitle("Console")
self.setModal(True)
self.setLayout(QtGui.QVBoxLayout())
# Text edit
# -------------------------------------------------------------------------
self.textEdit = QtGui.QPlainTextEdit(self)
self.textEdit.setReadOnly(True)
self.textEdit_cursor = QtGui.QTextCursor(self.textEdit.document())
self.layout().addWidget(self.textEdit)
# Ok / Close
# -------------------------------------------------------------------------
self.button_box = QtGui.QDialogButtonBox(self)
self.button_box.setStandardButtons(QtGui.QDialogButtonBox.Close)
self.button_box.button(QtGui.QDialogButtonBox.Close).setEnabled(False)
self.layout().addWidget(self.button_box)
# Connect definition
# -------------------------------------------------------------------------
self.connect(self.button_box.button(QtGui.QDialogButtonBox.Close),
QtCore.SIGNAL('clicked()'),
self.reject)
# Post-Initialization
# -------------------------------------------------------------------------
self.queue = queue.Queue()
# self.queue = queue.Queue(maxsize=2)
self.run_thread()
# Connect functions
# -----------------------------------------------------------------------------
def cancel_thread(self):
self.workerThread.stop()
def job_finished_from_thread(self, success):
self.workerThread.stop()
self.queue.get()
# Stop the pulsation
if self.queue.empty():
self.button_box.button(QtGui.QDialogButtonBox.Close).setEnabled(True)
self.emit(QtCore.SIGNAL('jobFinished(PyQt_PyObject)'), success)
def text_from_thread(self, value):
self.textEdit.insertPlainText(value)
cursor = self.textEdit.textCursor()
self.textEdit.setTextCursor(cursor)
def run_thread(self):
for index, url in enumerate(urls):
self.workerThread = LongRunningTask(parent_thread=self,
url=url,
filepath='C:\\Users\\philippe\\Downloads',
filename='url_%d.txt' % index,
index=index)
self.connect(self.workerThread,
QtCore.SIGNAL('jobFinished(PyQt_PyObject)'),
self.job_finished_from_thread)
self.connect(self.workerThread,
QtCore.SIGNAL('threadText(PyQt_PyObject)'),
self.text_from_thread)
self.queue.put(self.workerThread)
self.workerThread.start()
# If I set the queue to maxsize=2, how to manage it here
'''
while not self.queue.full():
self.queue.put(self.workerThread)
self.workerThread.start()
'''
# ---------------------------------------------------------------------------------
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Console()
window.show()
app.exec_()
Question:
Unfortunately, I encounter other types of difficulties. In reality, the queue can contain a large amount of threads (over 100). 1. How can I, like the QthreadPool and its setMaxThreadCount method, manage the number of threads running in parallel in order to prevent the system from collapsing completely ?