PyQt program randomly not responding without a Traceback - python

I wrote a program to batch change the filenames of many pictures, using PyQt5.
When I run it, it runs through some pictures successfully, and randomly crash without a Python Traceback, showing "has stopped working" in windows 8.1. Even if I use the same testcases, the program will sometimes crash after 20+ pictures, sometimes only 2, sometimes runs to the end.
I don't even know the program crashed in which line. How do I solve this problem?
Here is the program, I minimized the code that still work, and randomly crash as before.
# -*- coding: utf-8 -*-
import sys
import os
import re
from PyQt5 import Qt
if __name__ == '__main__':
application_Variable = Qt.QApplication(sys.argv)
from MainWindow import Ui_MainWindow_Widget
from FileName import Ui_FileName_Widget
class filename_class: # just a class to get path, filename, append
def __init__(self, fullpath):
self.re_path_temp = re.match(r".+/", fullpath)
self.path = self.re_path_temp.group(0)
self.name = fullpath[len(self.path):]
self.append = self.name[len(self.name[:self.name.rfind('.')])-len(self.name)+1:]
class fileNameDefine_Widget(Qt.QWidget): # the rename widget
def __init__(self, filename):
super(fileNameDefine_Widget, self).__init__()
self.fileWidget = Ui_FileName_Widget()
self.fileWidget.setupUi(self)
self.filename = filename
self.file_append = filename_class(self.filename).append # get the append
self.fileWidget.InputFileName_LineEdit.textChanged.connect(self.input_file_name_Change)
def input_file_name_Change(self):
self.export_name = self.fileWidget.InputFileName_LineEdit.text() + "." + self.file_append
self.fileWidget.ExportFileName_LineEdit.setText(self.export_name)
self.fileWidget.InputFileName_LineEdit.setEnabled(True)
class MainWindow_Class(Qt.QWidget): # the main widget
def __init__(self):
super(MainWindow_Class, self).__init__()
self.main = Ui_MainWindow_Widget() # init
self.main.setupUi(self)
self.root_directory = r"D:\TLCTest"
self.file_list = Qt.QFileDialog.getOpenFileNames(caption="Select file", directory=self.root_directory)[0]
self.count = 0 # count which file are being processed
self.show()
self.initiate_change_filename()
def initiate_change_filename(self):
file = self.file_list[self.count]
# show the picture
self.pixmap = Qt.QPixmap()
self.pixmap.load(file)
self.graphicsPixmapItem = Qt.QGraphicsPixmapItem(self.pixmap)
self.graphicsScene = Qt.QGraphicsScene()
self.graphicsScene.addItem(self.graphicsPixmapItem)
self.main.graphicsView.setScene(self.graphicsScene)
# start the rename widget
self.fileName_Widget = fileNameDefine_Widget(file)
self.fileName_Widget.fileWidget.InputFileName_LineEdit.returnPressed.connect(self.submit) # press enter to submit
self.fileName_Widget.show()
def submit(self):
filename = self.fileName_Widget.filename
path = filename_class(filename).path
final_name = self.fileName_Widget.fileWidget.ExportFileName_LineEdit.text()
os.rename(filename, path + final_name)
self.count += 1
if self.count == len(self.file_list):
exit()
else:
self.fileName_Widget.close()
self.initiate_change_filename()
if __name__ == '__main__':
my_Qt_Program = MainWindow_Class()
my_Qt_Program.show()
sys.exit(application_Variable.exec_())

As ekhumoro's comment, this problem was caused by repeatedly creating a fileNameDefine_Widget object like:
def initiate_change_filename(self):
self.fileName_Widget = fileNameDefine_Widget(file)
Instead, one should create this object once, and init it every time required, like:
class TLC_processor_Class(Qt.QWidget):
def init(self):
# Blabla
self.fileName_Widget = fileNameDefine_Widget(file)
def initiate_change_filename(self):
self.fileName_Widget.__init__(file)

Related

how to update a progress bar in a pyqt5 program and the pytube library [duplicate]

I have made a Desktop Application using Python and used PyQt5 and Pytube which could download video from youtube. When download is in Progress, I want to show user an animation. In Fact I did it, but when the file is getting downloaded the PyQt window seems like freezing and everything just gets paused until the download is complete. So, Does anyone know why is this happening? How do I fix it?
Here's the code snippet:
def download_created(self, qual): # Used in 'selection' method
selected_stream = yt.streams.get_by_resolution(qual)
self.progress_func()
try:
self.download_btn.setCurrentIndex(-1)
selected_stream.download(self.askLocation() + "/")
except:
pass
# This gets the quality that the user chooses
def selection(self):
global quality
quality = self.download_btn.currentText()
try:
self.download_created(quality) # Calls a method called 'download'
except:
self.start_anime()
# Fetching the details about the Link from Youtube
def download_youtube(self):
global check
if check != self.get_input():
check = self.get_input()
self.download_btn.clear()
enter_url = self.get_input()
try:
global yt
yt = pytube.YouTube(
enter_url,
on_progress_callback = on_progress,
on_complete_callback = self.complete_func)
self.start_anime()
except:
self.input_error()
VIDEO_TITLE = (yt.title)
global VIDEO_ID
VIDEO_ID = (yt.video_id)
videos = yt.streams.filter(mime_type="video/mp4", progressive="True")
# Display all the available qualities
for i in videos:
self.download_btn.addItem(i.resolution)
self.download_btn.currentIndexChanged.connect(self.selection)
You have to execute the time consuming tasks in another thread, for example in your case the task of getting the streams and downloading.
import sys
import threading
from functools import cached_property
from PyQt5 import QtCore, QtWidgets
import pytube
class QPyTube(QtCore.QObject):
initialized = QtCore.pyqtSignal(bool, str)
download_started = QtCore.pyqtSignal()
download_progress_changed = QtCore.pyqtSignal(int)
download_finished = QtCore.pyqtSignal()
def __init__(self, url):
super().__init__()
self._url = url
self._yt = None
self._mutex = threading.Lock()
threading.Thread(target=self._init, daemon=True).start()
#property
def url(self):
return self._url
#cached_property
def resolutions(self):
return list()
def _init(self):
with self._mutex:
self.resolutions.clear()
try:
self._yt = pytube.YouTube(
self.url,
on_progress_callback=self._on_progress,
on_complete_callback=self._on_complete,
)
streams = self._yt.streams.filter(mime_type="video/mp4", progressive="True")
except Exception as e:
self.initialized.emit(False, str(e))
return
with self._mutex:
self.resolutions = [stream.resolution for stream in streams]
self.initialized.emit(True, "")
def download(self, resolution, directory):
threading.Thread(
target=self._download, args=(resolution, directory), daemon=True
).start()
def _download(self, resolution, directory):
stream = self._yt.streams.get_by_resolution(resolution)
self.download_started.emit()
stream.download(directory)
def _on_progress(self, stream, chunk, bytes_remaining):
self.download_progress_changed.emit(
100 * (stream.filesize - bytes_remaining) // stream.filesize
)
def _on_complete(self, stream, filepath):
self.download_finished.emit()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.le_url = QtWidgets.QLineEdit("http://youtube.com/watch?v=2lAe1cqCOXo")
self.lbl_error = QtWidgets.QLabel()
self.btn_search = QtWidgets.QPushButton("Search")
self.cmb_resolutions = QtWidgets.QComboBox()
self.le_directory = QtWidgets.QLineEdit("")
self.btn_download = QtWidgets.QPushButton("Download")
self.pgb_download = QtWidgets.QProgressBar()
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
lay = QtWidgets.QGridLayout(central_widget)
lay.addWidget(self.le_url, 0, 0)
lay.addWidget(self.btn_search, 0, 1)
lay.addWidget(self.cmb_resolutions, 1, 0)
lay.addWidget(self.le_directory, 1, 1)
lay.addWidget(self.btn_download, 1, 2)
lay.addWidget(self.pgb_download, 2, 0, 1, 3)
self.btn_download.setEnabled(False)
self._qpytube = None
self.btn_search.clicked.connect(self.handle_search_clicked)
self.btn_download.clicked.connect(self.handle_download_clicked)
def handle_search_clicked(self):
self.cmb_resolutions.clear()
self.btn_search.setEnabled(False)
self.btn_download.setEnabled(False)
self.lbl_error.clear()
self._qpytube = QPyTube(self.le_url.text())
self._qpytube.initialized.connect(self.handle_initialized)
self._qpytube.download_progress_changed.connect(self.pgb_download.setValue)
self._qpytube.download_started.connect(self.handle_download_started)
self._qpytube.download_finished.connect(self.handle_download_finished)
#QtCore.pyqtSlot(bool, str)
def handle_initialized(self, status, error=""):
if status:
self.cmb_resolutions.addItems(self._qpytube.resolutions)
self.btn_download.setEnabled(True)
else:
self.lbl_error.setText(error)
self.btn_search.setEnabled(True)
def handle_download_clicked(self):
self._qpytube.download(
self.cmb_resolutions.currentText(), self.le_directory.text()
)
self.btn_search.setEnabled(False)
self.btn_download.setEnabled(False)
self.le_directory.setEnabled(False)
def handle_download_started(self):
self.lbl_error.clear()
print("started")
def handle_download_finished(self):
self.pgb_download.setValue(100)
self.btn_search.setEnabled(True)
self.btn_download.setEnabled(True)
self.le_directory.setEnabled(True)
print("finished")
def main(args):
app = QtWidgets.QApplication(args)
w = MainWindow()
w.show()
app.exec_()
if __name__ == "__main__":
main(sys.argv)

Why is my button PySide2 not working in Python?

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.

PyQt5 Threading - RecursionError: maximum recursion depth exceeded in comparison

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_())

Send file pointer to python thread and update file pointer

I have a python program with a thread and the thread should write into a file. I will spawn a thread from the main program. Now on new day trigger I will change the file pointer in the main program and I want the thread also to take the new file to write the data to the file.
I have a code which will take global variable and do this task. But is there any other better way of doing this?
#!/usr/bin/env python
import sys
import threading
import time
filePtr = None
import time
def fileWriteTh():
global filePtr
time.sleep(2)
filePtr.write("from the thrread this should in file 2")
def main():
global filePtr
filePtr = open("test1.txt","ab")
fileThread = threading.Thread(target=fileWriteTh)
fileThread.start()
if new_day_trigger:
filePtr.close()
filePtr = open("test2.txt","ab")
fileThread.join()
if __name__ == "__main__":
main()
This is the new code that is written:
#!/usr/bin/env python
import sys
import threading
import time
class SendPacket(object):
fileDesc = None
def __init__(self, fd):
super(SendPacket, self).__init__()
SendPacket.fileDesc = fd
def printFromInstance(self,var):
print var
SendPacket.fileDesc.write(var)
time.sleep(3)
print var
SendPacket.fileDesc.write(var)
def startabc(self, someVar):
self.printFromInstance(someVar)
#classmethod
def printVar(cls, printStr):
print printStr
cls.fileDesc.write(printStr)
#classmethod
def changeClsFile(cls, newFd):
cls.fileDesc = newFd
def main():
filePtr = open("test1.txt","ab")
sendPack_inst = SendPacket(filePtr)
fileThread = threading.Thread(target=sendPack_inst.startabc, args=("test1",))
fileThread.start()
time.sleep(2)
filePtr.close()
filePtr = open("test2.txt","ab")
SendPacket.changeClsFile(filePtr)
fileThread.join()
filePtr.close()
if __name__ == "__main__":
main()
Like this:
#!/usr/bin/env python
import sys
import thread
import time
class _fileACT :
def __init__(self):
self.trigger = 0
self.flag = True
self.msg = ""
self.files = (open("test1.txt","ab"),open("test2.txt","ab"))
def run(self,pssrg):
while self.flag :
if self.msg != "" :
self.files[self.trigger].write(self.msg)
self.msg = ""
def test(self,pssrg):
for i in range(20):
time.sleep(1)
if i %2 != 0 :
self.trigger = 0
elif i %2 != 1:
self.trigger = 1
self.msg = "%0.3d test-1,asdasdasd\n"%i
time.sleep(0.5)
print "wait..."
self.flag = False
for e in self.files : e.close()
print "can exit !"
if __name__ == "__main__":
fileACT = _fileACT()
thread.start_new_thread(fileACT.run,(None,))
thread.start_new_thread(fileACT.test,(None,))
We have three variables, filename, last opened file name and message. Two files, only False and True will be sufficient (of course you can use index for multiple files). We've written a test function into the class because we don't want our main cycle to freeze. The file selection is done with ' trigger ', but the previous and next file name is not the same, the previous closes.
The important point in the thread is that the time delay is strictly unavailable! The time delay is always applied to the trigger. The time delay cannot be placed in the main loop. An instance of access from outside the class is also attached. I hope it helps.

QThreadPool - How to interrupt / How to use wisely the waitForDone method

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 ?

Categories