Using GUI i am copying multiple files. for each file i am starting a thread. Is there is a way with which I can get a signal or response which tells that all the threads are done with their work. I need the response because depending on the response when all threads are done, I want to enable/disable few buttons and check boxes on the GUI.
for file_name in _src_files:
if (os.path.exists(_full_file_name)):
t = Thread(target=file_util.copy_file, args=[_full_file_name, _str_destination_dir, 'update=1'])
t.start()
You must create a class that inherits from QObject and have custom signals, for example:
def copy_file(source, destination, message, log):
[...]
log.finished.emit()
class Logger(QObject):
finished = pyqtSignal()
self.l = Logger()
for file_name in _src_files:
if (os.path.exists(_full_file_name)):
t = Thread(target=file_util.copy_file, args=[_full_file_name, _str_destination_dir, 'update=1'], self.l)
t.start()
self.l.finished.connect(some_slot)
Example:
import time
from threading import Thread
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Logger(QObject):
finished = pyqtSignal(str)
def myfunc(i, log):
print("sleeping 5 sec from thread %d" % i)
time.sleep(5)
print("finished sleeping from thread %d" % i)
log.finished.emit("message %d" % i)
class Widget(QWidget):
def __init__(self, *args, **kwargs):
QWidget.__init__(self, *args, **kwargs)
self.l = Logger()
self.counter_threads = 0
for i in range(10):
t = Thread(target=myfunc, args=(i, self.l))
t.start()
self.counter_threads += 1
self.l.finished.connect(self.some_slot)
def some_slot(self, message):
print("some_slot: "+ message)
self.counter_threads -= 1
if self.counter_threads == 0:
print("finished all threads")
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
Related
How can I reduce the number of pyqtSlot() functions so that I don't need to have two of each? It seems like there should be a better way of doing this, but I haven't been able to figure it out.
The code takes two files, reads each file on different threads, and prints the outputs to different QPlainTextEdit objects.
import sys
import time
import traceback
import pandas as pd
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from compare_files_gui import Ui_MainWindow
class WorkerSignals(QObject):
"""Defines signals from running worker thread."""
finished = pyqtSignal()
error = pyqtSignal(tuple)
result = pyqtSignal(str)
progress = pyqtSignal(str)
bar = pyqtSignal(int)
class Worker(QRunnable):
"""Worker thread."""
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
self.kwargs['progress_callback'] = self.signals.progress
self.kwargs['pbar'] = self.signals.bar
#pyqtSlot()
def run(self):
try:
result = self.fn(*self.args, **self.kwargs)
except:
traceback.print_exc()
exctype, value = sys.exc_info()[:2]
self.signals.error.emit((exctype, value, traceback.format_exc()))
else:
self.signals.result.emit(result)
finally:
self.signals.finished.emit()
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.pushButton.clicked.connect(self.compare)
self.ui.file1_progressBar.setValue(0)
self.ui.file2_progressBar.setValue(0)
self.ui.file1_lineEdit.setText('file1.csv')
self.ui.file2_lineEdit.setText('file2.csv')
self.file1 = self.ui.file1_lineEdit.text()
self.file2 = self.ui.file2_lineEdit.text()
self.threadpool = QThreadPool()
##### How can I consolidate the following slots so I don't need to
##### have 2, one for each console object?
#pyqtSlot(str)
def progress_fn1(self, n):
self.ui.console1_plainTextEdit.appendPlainText(n)
#pyqtSlot(str)
def progress_fn2(self, n):
self.ui.console2_plainTextEdit.appendPlainText(n)
#pyqtSlot(str)
def print_output1(self, s):
self.ui.console1_plainTextEdit.appendPlainText(s)
#pyqtSlot(str)
def print_output2(self, s):
self.ui.console2_plainTextEdit.appendPlainText(s)
#pyqtSlot()
def thread_complete1(self):
self.ui.console1_plainTextEdit.appendPlainText('Processing complete!')
#pyqtSlot()
def thread_complete2(self):
self.ui.console2_plainTextEdit.appendPlainText('Processing complete!')
#pyqtSlot(int)
def update_progress1(self, v):
self.ui.file1_progressBar.setValue(v)
#pyqtSlot(int)
def update_progress2(self, v):
self.ui.file2_progressBar.setValue(v)
def compare(self):
# files = [self.ui.file1_lineEdit.text(), self.ui.file2_lineEdit.text()]
files = [self.file1, self.file2]
# Start new thread for each file
for i, file in enumerate(files, 1):
worker = Worker(self.process_file, file)
#### Is there a better way to do this?
if i == 1:
worker.signals.progress.connect(self.progress_fn1)
worker.signals.result.connect(self.print_output1)
worker.signals.finished.connect(self.thread_complete1)
worker.signals.bar.connect(self.update_progress1)
elif i == 2:
worker.signals.progress.connect(self.progress_fn2)
worker.signals.result.connect(self.print_output2)
worker.signals.finished.connect(self.thread_complete2)
worker.signals.bar.connect(self.update_progress2)
else:
pass
# Execute thread
self.threadpool.start(worker)
def process_file(self, file, pbar, progress_callback):
"""Process file and emit signals."""
t0 = time.time()
progress_callback.emit(f'Processing {file}')
df = pd.read_csv(file, header=None, names=['col'])
num = len(df.index)
for i, (index, row) in enumerate(df.iterrows(), 1):
progress_callback.emit(' ' + row['col'])
pbar.emit(int(i*100/num))
time.sleep(0.25)
t1 = time.time()
return f'Time to complete: {round(t1-t0, 3)} s'
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
To fix the issue, I passed on the QPlainTextEdit and QProgressBar objects associated with each file to the thread, and then emitted those from the thread, so that I could determine which QPlainTextEdit to print to. From what I've read online, I don't think passing UI objects to background threads is a best practice, but it works for this simple application and I haven't been able to figure out a better way to do it.
import sys
import time
import traceback
import pandas as pd
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from compare_files_gui import Ui_MainWindow
class WorkerSignals(QObject):
"""Defines signals from running worker thread."""
finished = pyqtSignal(object)
error = pyqtSignal(tuple)
result = pyqtSignal(object, str)
progress = pyqtSignal(object, str)
bar = pyqtSignal(object, int)
class Worker(QRunnable):
"""Worker thread."""
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
# Store constructor arguments
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
# Add callbacks to kwargs
self.kwargs['progress_callback'] = self.signals.progress
self.kwargs['pbar_callback'] = self.signals.bar
#pyqtSlot()
def run(self):
try:
console, result = self.fn(*self.args, **self.kwargs)
except:
traceback.print_exc()
exctype, value = sys.exc_info()[:2]
self.signals.error.emit((exctype, value, traceback.format_exc()))
else:
self.signals.result.emit(console, result)
finally:
self.signals.finished.emit(console)
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# Initialize progress bars
self.ui.file1_progressBar.setValue(0)
self.ui.file2_progressBar.setValue(0)
# Connect widgets to slots
self.ui.pushButton.clicked.connect(self.compare)
# Create instance of QThreadPool
self.threadpool = QThreadPool()
#pyqtSlot(object, str)
def progress_fn(self, console, n):
"""Print progress string to specific QPlainTextEdit object."""
console.appendPlainText(n)
#pyqtSlot(object, str)
def print_output(self, console, s):
"""Print result string to specific QPlainTextEdit object."""
console.appendPlainText(s)
#pyqtSlot(object)
def thread_complete(self, console):
"""Print completion text to specific QPlainTextEdit object."""
console.appendPlainText('Processing complete!')
#pyqtSlot(object, int)
def update_progress(self, pbar, v):
"""Set value of QProgressBar object."""
pbar.setValue(v)
def compare(self):
"""Send each file and associated UI objects to thread."""
# Store files in list
# files = [self.ui.file1_lineEdit.text(), self.ui.file2_lineEdit.text()]
files = [self.file1, self.file2]
# Store QPlainTextEdit and QProgressBar objects in list
consoles = [self.ui.console1_plainTextEdit, self.ui.console2_plainTextEdit]
pbars = [self.ui.file1_progressBar, self.ui.file2_progressBar]
# Start new thread for each file
for i, (file, console, pbar) in enumerate(zip(files, consoles, pbars), 1):
worker = Worker(self.process_file, file, console, pbar)
# Connect thread signals to slots in UI thread
worker.signals.progress.connect(self.progress_fn)
worker.signals.result.connect(self.print_output)
worker.signals.finished.connect(self.thread_complete)
worker.signals.bar.connect(self.update_progress)
# Execute thread
self.threadpool.start(worker)
def process_file(self, file, console, pbar, pbar_callback, progress_callback):
"""Process file and emit signals."""
t0 = time.time()
progress_callback.emit(console, f'Processing {file}')
# Read file into dataframe
df = pd.read_csv(file, header=None, names=['col'])
# Iterate over each row and emit value of column and progress
num = len(df.index)
for i, (index, row) in enumerate(df.iterrows(), 1):
progress_callback.emit(console, ' ' + row['col'])
pbar_callback.emit(pbar, int(i*100/num))
# Slow down response
time.sleep(0.25)
t1 = time.time()
# Return QPlainTextEdit object and string
return console, f'Time to complete: {round(t1-t0, 3)} s'
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
So I currently have a PYQT application utilizing a combination of pythons built-in threading, and QT Qthread/worker system to allow for asynchronous activities
The code for generating the threads/workers is process_batch which is contained in a class called Scraper, which is a QObject
def process_batch(self, batch):
for worker in batch:
self.host.threads.append(QThread())
th = self.host.threads[len(self.host.threads)-1]
worker.moveToThread(th)
th.started.connect(worker.process)
qApp.aboutToQuit.connect(th.quit)
worker.finished.connect(worker.deleteLater)
th.finished.connect(th.deleteLater)
for thread in self.host.threads:
thread.start()
self.activeThreads += 1
the the self.host.threads is a list variable helf by the ScraperHost object, the batch variable is a standard python list with 50 of the following class:
class ScraperWorker(QObject):
complete = pyqtSignal(str)
start = pyqtSignal(str)
finished = pyqtSignal()
def __init__(self, url, task, _id):
QObject.__init__(self)
self.url = url
self.task = task
self.id = _id
self.start.connect(self.process)
def __str__(self):
return self.url
#pyqtSlot()
def process(self):
time.sleep(float(random.random() * random.randint(1,3)))
#self.complete.emit(str(self.id))
self.finished.emit()
The time.sleep was added to see if spacing out the thread's completions randomly would fix the issue, it did not
Originally I was running into an issue of the threads getting deleted before finishing when the process_batch function completed, so I implemented the self.host.threads to keep them from getting garbage collected.
threads are contained below in :
class ScraperHost(threading.Thread):
def __init__(self, threadID, urls, task):
threading.Thread.__init__(self)
self.threadID = threadID
self.scraper = Scraper(urls, task, self)
self.threads = []
def run(self):
self.scraper.run()
The scraper host is created in my MainWindow by:
def selectFileList(self):
options = QFileDialog.Options()
fileName, _ = QFileDialog.getOpenFileName(self, "Select List","", "Excel File (*.xls *.xlsx)", options=options)
if fileName:
...
...
self.ScraperHost = ScraperHost(0, urls, "index")
self.ScraperHost.run()
As it is currently I am able to run the function/program with zero errors, but it only completes exactly half of the threads prior to finishing. It gives me no errors, messages, or any indication as to why they aren't finishing. If I increase the batch size to 100, it finishes 50. If I run it in debug mode in VS Code, i can see that all 50 threads are created correctly, and are all in the host. threads variable. If ** I step through the thread's creation 1 by 1 in debug mode they will all finish correctly**, but if I just run the program normally only exactly half will finish. The 25 threads that finish are different every time due to the random time they wait. The self.finished.emit() of the worker just prints the thread's ID, which is how I am seeing how many finish
EDIT: Reproducable Sample: Side Note: Gets through more, sometimes all, but with no consistency, of the threads when ran from idle as opposed to VS Code
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import threading
import time
import random
scraperhost = None
class ScraperHost(threading.Thread):
def __init__(self, threadID, urls, task):
threading.Thread.__init__(self)
self.threadID = threadID
self.scraper = Scraper(urls, task, self)
self.threads = []
def run(self):
self.scraper.run()
class Scraper(QObject):
output = pyqtSignal(str)
complete = pyqtSignal()
def __init__(self, urls, task, host):
QObject.__init__(self)
self.urls = urls
self.batches = []
self.activeThreads = 0
self.task = task
self.host = host
def __del__(self):
self.wait()
def run(self):
batch_size = 50
batch_count = 1
for batch in range(0, batch_count):
self.batches.append([])
for i in range (0, batch_size):
try:
c_url = "www.website.com"
worker = ScraperWorker(c_url, self.task, i)
worker.complete.connect(self.worker_finished)
self.batches[batch].append(worker)
except IndexError:
break
if len(self.batches) == 0:
return
self.process_batch(self.batches.pop())
#pyqtSlot(str)
def worker_finished(self, ss):
self.activeThreads -= 1
print(self.activeThreads)
def process_batch(self, batch):
for worker in batch:
self.host.threads.append(QThread())
th = self.host.threads[len(self.host.threads)-1]
worker.moveToThread(th)
th.started.connect(worker.process)
qApp.aboutToQuit.connect(th.quit)
worker.finished.connect(worker.deleteLater)
th.finished.connect(th.deleteLater)
for thread in self.host.threads:
thread.start()
self.activeThreads += 1
class ScraperWorker(QObject):
complete = pyqtSignal(str)
start = pyqtSignal(str)
finished = pyqtSignal()
def __init__(self, url, task, _id):
QObject.__init__(self)
self.url = url
self.task = task
self.id = _id
self.start.connect(self.process)
def __str__(self):
return self.url
#pyqtSlot()
def process(self):
time.sleep(float(random.random() * random.randint(1,3)))
self.complete.emit(str(self.id))
self.finished.emit()
def main():
app = QApplication([])
ex = QWidget()
ex.show()
global scraperhost
scraperhost = ScraperHost(0, [], "index")
scraperhost.start()
app.exec_()
return
main()
I have a program which compares to DB table values and i have created a GUI in PyQt5. I have created two threads one for querying each table and then program has to wait till two threads are completed. My code below
from PySide2 import QtWidgets
from PySide2 import QtGui
from PySide2 import QtCore
from Main_interface import Ui_mainWindow
import pandas as pd
class mainWindow(QtWidgets.QMainWindow, Ui_mainWindow):
sqlClicked1 = QtCore.Signal(str)
sqlClicked2 = QtCore.Signal(str)
def __init__(self, parent=None):
super(mainWindow, self).__init__(parent)
self.setupUi(self)
self.thread = QtCore.QThread(self)
self.thread.start()
self.obj = Worker()
self.obj.moveToThread(self.thread)
self.sqlClicked.connect(self.obj.runsql_MC)
self.sqlClicked1.connect(self.obj.runsql_IRI)
self.obj.error.connect(self.on_error)
def run_report(self):
sqlquery1 = "Select * from table1"
sqlquery2 = "Select * from table2"
df1 = self.sqlClicked1.emit(sqlquery1)
df2 = self.sqlClicked2.emit(sqlquery2)
self.sqlClicked1.finished.connect(self.on_finished)
self.sqlClicked2.finished.connect(self.on_finished)
print("SQL execution is done")
#Then i am calling function to compare two dataframes
class Worker(QtCore.QObject):
finished = QtCore.Signal()
result = QtCore.Signal(object)
#QtCore.Slot(str)
def runsql_MC(self, sqlquery_MC):
print("Thread1 is working")
try:
df1 = pd.read_sql(sql=sqlquery_MC, con=cnxn)
except:
traceback.print_exc()
else:
self.signals.result.emit(df1) # Return the result of the processing
finally:
self.signals.finished.emit() # Done
#QtCore.Slot(str)
def runsql_IRI(self, sqlquery_IRI):
print("Thread2 is working")
try:
df2 = pd.read_sql(sql=sqlquery_IRI, con=cnxn)
except:
traceback.print_exc()
else:
self.signals.result.emit(df2)
finally:
self.signals.finished.emit()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
my_app = mainWindow()
my_app.show()
sys.exit(app.exec_())
self.sqlClicked1.emit(sqlquery1) and self.sqlClicked2.emit(sqlquery2) is calling corresponding threads runsql_MC() and runsql_IRI. Then I need to wait till two threads are completed to start comparison process. Currently its not happening.
Although your code is not an MRE, show your ignorance of various concepts.
The emission of a signal does not imply obtaining the data as a result since it will be sent asynchronously.
In your code even if you invoke 2 queries does not imply that each one runs on different threads since the worker lives in a single thread.
Your runsql_MC and runsql_IRI methods are redundant since they are a template of the same thing.
In addition to other errors such as that there is no object/signal called sqlClicked, you have not declared the object signals, etc.
The idea is to have a worker who lives in a different thread for each query, and create a class that handles the workers waiting for the data and eliminating when they have finished their work.
from functools import partial
import sqlite3
import pandas as pd
from PySide2 import QtCore, QtGui, QtWidgets
class Worker(QtCore.QObject):
finished = QtCore.Signal()
result = QtCore.Signal(object)
#QtCore.Slot(str)
def runsql(self, query):
cnxn = sqlite3.connect("test.db")
print("Thread1 is working")
try:
df1 = pd.read_sql(sql=query, con=cnxn)
except:
traceback.print_exc()
else:
self.result.emit(df1) # Return the result of the processing
finally:
self.finished.emit() # Done
class SqlManager(QtCore.QObject):
results = QtCore.Signal(list)
def __init__(self, parent=None):
super().__init__(parent)
self.workers_and_threads = {}
self.dataframes = []
def execute_queries(self, queries):
for query in queries:
thread = QtCore.QThread(self)
thread.start()
worker = Worker()
worker.result.connect(self.onResults)
worker.moveToThread(thread)
self.workers_and_threads[worker] = thread
# launch task
wrapper = partial(worker.runsql, query)
QtCore.QTimer.singleShot(0, wrapper)
#QtCore.Slot(object)
def onResults(self, result):
worker = self.sender()
thread = self.workers_and_threads[worker]
thread.quit()
thread.wait()
del self.workers_and_threads[worker]
worker.deleteLater()
self.dataframes.append(result)
if not self.workers_and_threads:
self.results.emit(self.dataframes)
self.dataframes = []
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.push_button = QtWidgets.QPushButton("Run Report")
self.push_button.clicked.connect(self.run_report)
self.setCentralWidget(self.push_button)
self.manager = SqlManager(self)
self.manager.results.connect(self.onResults)
#QtCore.Slot()
def run_report(self):
sqlquery1 = "Select * from table1"
sqlquery2 = "Select * from table2"
queries = [sqlquery1, sqlquery2]
self.manager.execute_queries(queries)
self.push_button.setEnabled(False)
#QtCore.Slot(list)
def onResults(self, dataframes):
print(dataframes)
self.push_button.setEnabled(True)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
my_app = MainWindow()
my_app.show()
sys.exit(app.exec_())
I'm not quite sure if it is written in your code but Have you ever uses a join() sentence to wait till those threads end? You should be using it when you awake those two threads inside your def __init__() method from your mainWindow class
I have in an application in python a worker thread that prevents the GUI from being frozen, since it requires continuous processing in the application. The worker thread calls the "doing" function on the main thread that often has heavy processing.
The problem is that when a heavy processing happens the thread does not wait for the code of the "doing" function to be executed and executes the function again.
Code:
import time
import sys
from PySide import QtCore, QtGui
class ServerThread(QtCore.QThread):
def __init__(self, parent=None):
QtCore.QThread.__init__(self)
self.x = 0
self.sleep_time = 1
def start_server(self):
while True :
print "\nBegin Thread: "+str(self.x)
self.emit(QtCore.SIGNAL("dosomething(int)"), self.x)
time.sleep(self.sleep_time)
print "End Thread: "+str(self.x)
self.x += 1
def run(self):
self.start_server()
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.thread = ServerThread()
self.thread.start()
self.connect(self.thread, QtCore.SIGNAL("dosomething(int)"), self.doing)
def doing(self, x):
print "--Begin Process: " + str(x)
#Does some heavy processing
h = 0
for i in range(30000000) :
h = x ** 2
print "--End Process: " + str(x)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
frame = MainWindow()
frame.show()
app.exec_()
I need the "doing" function to be executed only when the last one has been executed completely.
Image 1 shows the current output and picture 2 shows the expected output.
Current Output:
Expected output:
Please explain how do we send/receive data from Thread managed by Queue....
First I subclass 'QThread' defining its run() method which is started when QThread's.start() is called:
class SimpleThread(QtCore.QThread):
def __init__(self, queue, parent=None):
QtCore.QThread.__init__(self, parent)
self.queue=queue
def run(self):
while True:
arg=self.queue.get()
self.fun(arg)
self.queue.task_done()
def fun(self, arg):
for i in range (3):
print 'fun: %s'%i
self.sleep(1)
return arg+1
Then I declare two Thread instances (so only two CPU cores are taken) sending self.queue instance as an argument.
self.queue=queue.Queue()
for i in range(2):
thread=SimpleThread(self.queue)
thread.start()
Now if I understand it correctly thread.start() is not starting anything. The real "start" happens only when I call queue.put():
for arg in [1,2,3]: self.queue.put(arg)
This last line is what makes a "real" call. Aside from creating and starting Queue item put() allows to save any arbitrary value to each Queue item. .put() does several things at once: it creates, it starts, it moves the processing through the Queue and it allows to place a variable "inside" of the queue item (which later can be retrieved from inside of the function-processor: using Queue item's '.get()` method).
But how do I return the value from fun() function. A "regular" fun()'s return resultValue doesn't work. And I can't use self.queue.put() method since this method aside from storing a data "creates" a new queue item...
EDITED LATER:
Here is slightly tweaked code (copy/pasted from another post) showing an approach on how to return a value from completed Thread. I am not sure if the the approach used here would work with QThread... please correct me if I am wrong:
import os, sys
import threading
import Queue
def callMe(incomingFun, daemon=False):
def execute(_queue, *args, **kwargs):
result=incomingFun(*args, **kwargs)
_queue.put(result)
def wrap(*args, **kwargs):
_queue=Queue.Queue()
_thread=threading.Thread(target=execute, args=(_queue,)+args, kwargs=kwargs)
_thread.daemon=daemon
_thread.start()
_thread.result_queue=_queue
return _thread
return wrap
#callMe
def localFunc(x):
import time
x = x + 5
time.sleep(5)
return x
thread=localFunc(10)
# this blocks, waiting for the result
result = thread.result_queue.get()
print result
In normal circumstances you'd use a result queue to send results back, and then have some other thread running that waits for the results:
class SimpleThread(QtCore.QThread):
def __init__(self, queue, result_queue, parent=None):
QtCore.QThread.__init__(self, parent)
self.queue=queue
self.result_queue = result_queue
def run(self):
while True:
arg=self.queue.get()
self.fun(arg)
self.queue.task_done()
def fun(self, arg):
for i in range (3):
print 'fun: %s'%i
self.sleep(1)
self.result_queue.put(arg+1)
def handle_results(result_queue):
while True:
result = result_queue.get()
print("Got result {}".format(result))
Main thread:
self.queue=queue.Queue()
self.result_queue = queue.Queue()
result_handler = threading.Thread(target=handle_results, self.result_queue)
for i in range(2):
thread=SimpleThread(self.queue, self.result_queue)
thread.start()
Doing it this way will keep you from blocking the GUI's event loop while you wait for the results. Here's what the equivalent would look like with multiprocessing.pool.ThreadPool:
from multiprocessing.pool import ThreadPool
import time
def fun(arg):
for i in range (3):
print 'fun: %s'%i
time.sleep(1)
return arg+1
def handle_result(result):
print("got result {}".format(result))
pool = ThreadPool(2)
pool.map_async(fun, [1,2,3], callback=handle_result)
Which is a lot simpler. It internally creates a result handling thread, which will automatically call handle_result for you when fun completes.
That said, you're using QThread, and you want the results to update GUI widgets, so you really want your results to be sent back to the main thread, not to a result handling thread. In that case, it makes sense to use Qt's signaling system, so that you can safely update the GUI when you receive the result:
from PyQt4 import QtCore, QtGui
import sys
import Queue as queue
class ResultObj(QtCore.QObject):
def __init__(self, val):
self.val = val
class SimpleThread(QtCore.QThread):
finished = QtCore.pyqtSignal(object)
def __init__(self, queue, callback, parent=None):
QtCore.QThread.__init__(self, parent)
self.queue = queue
self.finished.connect(callback)
def run(self):
while True:
arg = self.queue.get()
if arg is None: # None means exit
print("Shutting down")
return
self.fun(arg)
def fun(self, arg):
for i in range(3):
print 'fun: %s' % i
self.sleep(1)
self.finished.emit(ResultObj(arg+1))
class AppWindow(QtGui.QMainWindow):
def __init__(self):
super(AppWindow, self).__init__()
mainWidget = QtGui.QWidget()
self.setCentralWidget(mainWidget)
mainLayout = QtGui.QVBoxLayout()
mainWidget.setLayout(mainLayout)
button = QtGui.QPushButton('Process')
button.clicked.connect(self.process)
mainLayout.addWidget(button)
def handle_result(self, result):
val = result.val
print("got val {}".format(val))
# You can update the UI from here.
def process(self):
MAX_CORES=2
self.queue = queue.Queue()
self.threads = []
for i in range(MAX_CORES):
thread = SimpleThread(self.queue, self.handle_result)
self.threads.append(thread)
thread.start()
for arg in [1,2,3]:
self.queue.put(arg)
for _ in range(MAX_CORES): # Tell the workers to shut down
self.queue.put(None)
app = QtGui.QApplication([])
window = AppWindow()
window.show()
sys.exit(app.exec_())
Output when the button is pushed:
fun: 0
fun: 0
fun: 1
fun: 1
fun: 2
fun: 2
fun: 0
got val 2
got val 3
Shutting down
fun: 1
fun: 2
Shutting down
got val 4