Reputation: 6147
How does qthread manage how many are being processed when I only have 4 threads max? For example say I have 100 long processing tasks. What happens when I for loop through my 100 tree view items and emit a Qthread to process the each task? Does it automatically determine I only have 4 threads and process the 100 threads within the 4 available?
Curious. Thanks
import os
import sys
import time
import random
from PySide2 import QtCore, QtGui, QtWidgets
class TaskThread(QtCore.QThread):
finished = QtCore.Signal(object)
def __init__(self, index):
super(TaskThread, self).__init__()
self._index = index
def runTask(self, task):
self._task = task
def run(self):
t = random.randint(0,4)
time.sleep(t)
self.finished.emit(self._index)
class ExampleDialog(QtWidgets.QDialog):
def __init__(self):
super(ExampleDialog, self).__init__()
self.itemModel = QtGui.QStandardItemModel()
self.uiListView = QtWidgets.QListView()
self.uiListView.setModel(self.itemModel)
self.mainLayout = QtWidgets.QVBoxLayout(self)
self.mainLayout.addWidget(self.uiListView)
self.populateItems()
def populateItems(self):
self.threads = []
for x in range(100):
output = 'C:/Users/JokerMartini-Asus/Desktop/Trash/thumbs/IMG_409{}.jpg'.format(x)
# Item
item = QtGui.QStandardItem('{}'.format(x))
item.setData(QtGui.QPixmap(), QtCore.Qt.DecorationRole)
item.setData(output, QtCore.Qt.UserRole)
self.itemModel.appendRow(item)
mIndex = QtCore.QPersistentModelIndex(self.itemModel.indexFromItem(item))
tt = TaskThread(mIndex)
self.threads.append(tt)
tt.start()
tt.finished.connect(self.processFinished)
def processFinished(self, index):
if index.isValid():
model = index.model()
model.setData(index, '{} updated'.format(index.row()), QtCore.Qt.DisplayRole)
if __name__ == '__main__':
pass
app = QtWidgets.QApplication(sys.argv)
window = ExampleDialog()
window.resize(300,600)
window.show()
window.raise_()
sys.exit(app.exec_())
Upvotes: 0
Views: 111
Reputation: 48260
Note: I'm basing this answer only to empirical experimentation and basic knowledge of threads. I don't have enough knowledge on the lower implementation of threads to provide reliable resources of what I'm expressing, nor I can certify that this is valid in the same way on all platforms Qt supports (I'm on Linux).
There are two important things to consider when dealing with threading, even with Qt bindings:
Fundamentally speaking, there's no absolute order, and in python there's no strict priority for a thread (due to the above reasons): processing is theoretically done [almost] in the order of thread starting, but the results may vary depending on how "fast" the processing is done for each thread, even for identical operations. As soon as a thread releases control to the GIL, the "next" thread gets it.
You could see a slightly more reliable result by adding the following changes to your code:
class TaskThread(QtCore.QThread):
# ...
def run(self):
# always use the same timeout
time.sleep(1)
self.finished.emit(self._index)
class ExampleDialog(QtWidgets.QDialog):
# ...
def processFinished(self, index):
while app.hasPendingEvents():
app.processEvents()
time.sleep(.05)
self.uiListView.repaint()
# ...
Note: the above code will block the UI, hasPendingEvents
has been considered obsolete exactly due to the concurrency of threading and the usage processEvents
is discouraged for the same reason; consider it only for educational purposes.
This means three things:
True parallelism can only be achieved through multiprocessing, but the problem is that processes, unlike threads, don't share memory. This doesn't mean that it's completely impossible: this link claims they achieved a proper multiprocessing support for Qt signal/slot mechanism, but I never tried it and I cannot say it actually works (especially considering that that link is quite old).
Note: you shouldn't connect the signals of a thread after it has started, especially if the thread can possibly return immediately (time.sleep(0)
); move the tt.finished.connect
line before tt.start()
.
Upvotes: 1