How can I send a list object from a QThread thread to the UI's main thread? - multithreading

I have written this example code to try and figure out how to communicate between a background thread, and the main thread. As I understand it, a thread cannot simply interact with the UI or variables existing in a different thread.
I would like to grab the list 'data' in the background thread, then display it on 'lbl2' in the UI. If this program is run as is, it will print the result I want on the command line.
Data received in background thread = [1, 2, 3, 4, 5]
Q1: What is the correct way to send data such as a list or string, from the background thread to the main thread?
Q2: How would I begin to implement that in the example code?
#!/usr/bin/env python3.4
from PySide.QtGui import QPushButton, QApplication, QWidget, QLabel
from PySide.QtCore import QThread, QCoreApplication
import queue
import sys
class Gui(QWidget):
def __init__(self):
super(Gui, self).__init__()
self.initUI()
def initUI(self):
lbl1 = QLabel('Data Recieved =', self)
lbl2 = QLabel('None', self)
lbl2.move(85, 0)
lbl2.resize(100, 15)
qbtn = QPushButton('Quit', self)
qbtn.clicked.connect(QCoreApplication.instance().quit)
qbtn.move(0, 20)
btn = QPushButton('Get Summary', self)
btn.move(100, 20)
btn.clicked.connect(lambda: bgThread.summary())
self.setGeometry(300, 300, 200, 50)
self.setWindowTitle('Thread Communication Example')
self.show()
class BackgroundThread(QThread):
def __init__(self, q, loop_time=1.0/60):
self.q = q
self.timeout = loop_time
super(BackgroundThread, self).__init__()
def onThread(self, function, *args, **kwargs):
self.q.put((function, args, kwargs))
def run(self):
while True:
try:
function, args, kwargs = self.q.get(timeout=self.timeout)
function(*args, **kwargs)
except queue.Empty:
self.idle()
def idle(self):
pass
def _summary(self):
# Run function which will return a list object
# data = externalclass.summary()
# Need to send list:'data' to the main thread.
data = [1, 2, 3, 4, 5]
print('Data received in background thread =', data)
def summary(self):
self.onThread(self._summary)
if __name__ == "__main__":
app = QApplication(sys.argv)
# Setup background thread
request_queue = queue.Queue()
bgThread = BackgroundThread(request_queue)
bgThread.start()
# Setup Gui
ui = Gui()
sys.exit(app.exec_())

You can define a custom signal, which can be safely emitted across threads:
from PySide.QtCore import Signal
class Gui(QWidget):
def initUI(self):
...
bgThread.dataReceived.connect(lambda data: lbl2.setText(str(data)))
class BackgroundThread(QThread):
dataReceived = Signal(list)
...
def _summary(self):
...
self.dataReceived.emit(data)

Related

PyQt5 multithreading: How to combine output of multiple threads

I am trying to build a Python GUI using PyQt5 which incorporates multi-threading. Using various snippets of code (acquired mainly from Stack Overflow), I have come up with the code below which reads data from a csv file into a pandas dataframe and updates the GUI (triggered from a QTimer).
I would like to implement a 2nd thread which reads from a different csv file and a function which accepts both dataframes and does some computation on them before updating the GUI. I'm not sure how I can do this within the signals/slots framework of PyQt5. Any help is much appreciated!
import time
import traceback
import sys
import pandas as pd
import numpy as np
from PyQt5.QtWidgets import (QWidget, QTabWidget, QGridLayout, QApplication, QMainWindow, QStatusBar, QTableView)
from PyQt5.QtCore import (Qt, QTimer, QAbstractTableModel, QThread, QVariant, QObject, QRect, pyqtSlot, pyqtSignal)
class PandasModel(QAbstractTableModel):
"""
Class to populate a table view with a pandas dataframe
"""
def __init__(self, data, parent=None):
QAbstractTableModel.__init__(self, parent)
self._data = np.array(data.values)
self._cols = data.columns
self.r, self.c = np.shape(self._data)
def rowCount(self, parent=None):
return self.r
def columnCount(self, parent=None):
return self.c
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole:
return self._data[index.row(), index.column()]
return None
def headerData(self, p_int, orientation, role):
if role == Qt.DisplayRole:
try:
if orientation == Qt.Horizontal:
return self._cols[p_int]
elif orientation == Qt.Vertical:
return p_int
except(IndexError, ):
return QVariant()
else:
return QVariant()
class WorkerSignals(QObject):
"""
Defines the signals available from a running worker thread.
Supported signals are:
finished: No data
error: `tuple` (exctype, value, traceback.format_exc() )
result: `object` data returned from processing, anything
progress: `int` indicating % progress
"""
finished = pyqtSignal()
error = pyqtSignal(tuple)
result = pyqtSignal(object)
progress = pyqtSignal(int)
class Worker(QObject):
"""
Worker thread
Inherits from QRunnable to handler worker thread setup, signals and wrap-up.
:param callback: The function callback to run on this worker thread. Supplied args and
kwargs will be passed through to the runner.
:type callback: function
:param args: Arguments to pass to the callback function
:param kwargs: Keywords to pass to the callback function
"""
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
# Store constructor arguments (re-used for processing)
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
# Add the callback to our kwargs
# kwargs['progress_callback'] = self.signals.progress
#pyqtSlot()
def run(self):
"""
Initialise the runner function with passed args, kwargs.
"""
# Retrieve args/kwargs here; and fire processing using them
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) # Return the result of the processing
finally:
self.signals.finished.emit() # Done
class MyTabWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
self.layout = QGridLayout(self)
# Initialize tab screen
self.tabs = QTabWidget()
self.tab1 = QWidget()
self.tabs.resize(300, 200)
# Add tabs
self.tabs.addTab(self.tab1, "Tab 1")
# Create tabs
self.tab1.layout = QGridLayout(self)
self.tab1.setLayout(self.tab1.layout)
# Create table widgets
self.table_widget1 = MyTableWidget(self)
# Add tables to tabs
self.tab1.layout.addWidget(self.table_widget1)
# Add tabs to widget
self.layout.addWidget(self.tabs)
self.setLayout(self.layout)
class MyTableWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
self.layout = QGridLayout(self)
# Initialize tables
self.table = QTableView()
# Add tabs to widget
self.layout.addWidget(self.table)
self.setLayout(self.layout)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
# Create a central Widgets
self.central_widget = QWidget()
# Create tab Widgets
self.tab_widget = MyTabWidget(self)
# Create a Layout for the central Widget
self.central_layout = QGridLayout()
# Set the Layout
self.central_widget.setLayout(self.central_layout)
# Set the Widget
self.setCentralWidget(self.central_widget)
self.central_layout.addWidget(self.tab_widget)
self.setGeometry(QRect(0, 100, 2000, 1500))
self.setWindowTitle('CSV viewer')
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
self.proxy = None
self.model = None
self.show()
# Setup worker thread
self.worker_thread = QThread(self)
self.worker = Worker(self.read_csv)
self.worker.moveToThread(self.worker_thread)
self.worker_thread.started.connect(self.worker.run)
# Connect worker signals to appropriate ports
self.worker.signals.result.connect(self.process_csv)
self.worker.signals.finished.connect(self.worker_thread.quit)
self.worker.signals.finished.connect(self.print_refresh_time)
# Setup timer to repetitively trigger start of worker thread e.g. every 5000ms
self.timer = QTimer()
self.timer.timeout.connect(self.worker_thread.start)
self.timer.start(5000)
def process_csv(self, df):
self.model = PandasModel(df)
self.tab_widget.table_widget1.table.setModel(self.model)
self.tab_widget.table_widget1.table.resizeColumnsToContents()
def print_refresh_time(self):
self.status_bar.showMessage('Last updated: ' + time.strftime("%d/%m/%Y %H:%M:%S", time.localtime()))
#staticmethod
def read_csv():
df = pd.read_csv("C:\\Users\\Brian\\Desktop\\E0.csv")
return df
def main():
app = QApplication(sys.argv)
window = MainWindow()
app.exec_()
if __name__ == '__main__':
main()

How to write a multi-threaded Pyside application

What is the simplest way to multi-thread a Pyside application, so the GUI can be operational and the thread will still run?
Thread class:
class MyLongThread(QThread):
def __init__(self, parent = None):
QThread.__init__(self, parent)
self.exiting = False
def run(self):
while 1:
self.msleep(100)
print("run")
Complete .pyw
import sys,time
from PySide.QtGui import *
from PySide.QtCore import *
from PySide.QtWebKit import *
def thread():
global threade
threade = MyLongThread()
threade.run()
def thread_terminate():
global threade
threade.terminate()
class MyLongThread(QThread):
def __init__(self, parent = None):
QThread.__init__(self, parent)
self.exiting = False
def run(self):
while 1:
self.msleep(100)
print("run")
app = QApplication(sys.argv)
wid = QWidget()
wid.resize(250, 400)
wid.setWindowTitle('Threaded Program')
#wid.setWindowIcon(QIcon('web.png'))
#### BUTTONS
btn = QPushButton('Stop', wid)
btn.setToolTip('Stop the thread.') ## Stop the thread
btn.resize(btn.sizeHint())
btn.move(147, 50)
btn.clicked.connect(thread_terminate)
qbtn = QPushButton('Start', wid)
qbtn.setToolTip('Start the thread.') ## End the Thread
qbtn.resize(btn.sizeHint())
qbtn.move(27, 50)
qbtn.clicked.connect(thread)
####
#### LABEL
label = QLabel('Start The Thread :)',wid)
label.resize(label.sizeHint())
label.move(28, 15)
####
wid.show()
sys.exit(app.exec_())
When I run the code and press the start button, it freezes the gui but prints run.
Don't directly call thread.run() as this executes the method in the main thread. Instead, call thread.start() which will launch a thread and start executing your run() method in the thread.

PySide: Threading causes GUI to crash

I'm trying to learn the basics of threading with PySide, and so put together the below code. What I'm trying to do is launch a thread that will update a QPlainTextEdit widget using a list of string, with a delay between each string. Instead what I'm getting is a crash to desktop, and I can't understand why:
import sys
import time
from PySide import QtCore, QtGui
class Worker(QtCore.QThread):
to_log = QtCore.Signal(str)
def __init__(self, txt, parent=None):
super(Worker, self).__init__(parent)
self.txt = txt
def run(self):
for i in self.txt:
self.to_log.emit(i)
time.sleep(1)
class TestThreadsApp(QtGui.QWidget):
def __init__(self):
super(TestThreadsApp, self).__init__()
self.initUI()
def initUI(self):
self.log = QtGui.QPlainTextEdit()
self.pb = QtGui.QPushButton('Go')
hbox = QtGui.QHBoxLayout()
hbox.addWidget(self.log)
hbox.addWidget(self.pb)
self.setLayout(hbox)
self.setGeometry(300, 300, 300, 150)
self.setWindowTitle('Test')
self.show()
self.pb.clicked.connect(self.get_worker)
def get_worker(self):
self.proceed = False
worker = Worker(['This is a test', 'to understand threading'])
worker.to_log.connect(self.to_log)
worker.start()
def to_log(self, txt):
self.log.appendPlainText(txt)
def main():
app = QtGui.QApplication(sys.argv)
ex = TestThreadsApp()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
If I update the get_worker() method to the below, it will run, but the QPlainTextEdit widget is updated with all the strings simultaneously, where as the behavior I'm wanting is for the widget to be updated by the threaded processes as each string is emitted - not altogether after both have been emitted:
def get_worker(self):
self.proceed = False
worker = Worker(['This is a test', 'to understand threading'])
worker.to_log.connect(self.to_log)
worker.start()
while not worker.isFinished():
pass
You need to keep a reference to the thread, otherwise it will be garbage-collected as soon as get_worker returns.
So do something like this, instead:
def get_worker(self):
self.worker = Worker(['This is a test', 'to understand threading'])
self.worker.to_log.connect(self.to_log)
self.worker.start()

call function of main thread from secondary thread

I am making a GUI in PyQt for user to create backup of huge data.
The GUI ( main thread ) is taking inputs from user. rsync command ( for backup ) is also being called in main thread hence the window is freezing.
Aim is to try qthread such that app runs without freezing.
My search material :
1 : https://www.youtube.com/watch?v=o81Q3oyz6rg. This video shows how to not freeze GUI by running other task in secondary thread. I've tried it and it works. But it does not help in running the command in worker thread.
Inspite of calling rsync in secondary thread, the gui still freezes. What am I doing wrong ?.
import sys
from PyQt4 import QtCore, QtGui
from backUpUi import Ui_MainWindow
import threading, Queue
class callerThread(QtCore.QThread):
def __init__(self, func, parent=None, *args, **kwargs):
super(callerThread, self).__init__(parent)
self._func = func
self._args = args
self._kwargs = kwargs
def run(self):
self._func(*self._args, **self._kwargs)
class Monitor(QtCore.QObject):
updateText = QtCore.pyqtSignal(str)
def update_list(self):
t_monitor = callerThread(self.monitor_vector, parent=self)
t_monitor.daemon = True
t_monitor.start()
def monitor_vector(self):
self.updateText.emit('updated list')
class backUpMain(QtGui.QMainWindow):
def __init__(self,parent=None):
super(backUpMain, self).__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.connect(self.ui.okButton, QtCore.SIGNAL("clicked()"), self.startThread)
self.ui.cancelButton.released.connect(sys.exit)
self.monitor = Monitor()
def _handlebackUpdate(self, txt):
QtGui.QMessageBox.information(self, "thread started!", txt)
self.ui.logEdit.clear()
self.ui.logEdit.setText(txt)
def startThread(self):
self.monitor = Monitor()
self.monitor.updateText.connect(self._handlebackUpdate)
self.monitor.update_list()
def threadDone(self,text1):
self.ui.logEdit.append("Worker Thread finished processing %s" % text1)
def exitWindow(self):
self.ui.close()
def main():
app = QtGui.QApplication(sys.argv)
dialog = backUpMain()
dialog.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
While searching for answer, qtcentre helped with it.
You need to have seperate class for signals
class MySignal(QtCore.QObject):
sig = QtCore.pyqtSignal(list)
sigStr = QtCore.pyqtSignal(str)
This signal is used to communicate between threads.
to communicate from main thread to worker thread,
create instance of qthread in init of class where Ui is defined
to pass parameters from main thread either in init or where required.
class MyThread(QtCore.QThread):
def __init__(self, parent = None, *args, **kw): . .
self.setData(*args, **kw)
def setData(self, userShotList, inData, outData, dept):
self.userShotList = userShotList . .
This way data is passed from main to worker.
to communicate from worker thread to main thread
class MyThread(QtCore.QThread):
def __init__(self, parent = None, *args, **kw): . . . .
self.signal = MySignal()
where required execute signal with different types ( list, str ...)
defined in MySignal()
def xyz(self): self.signal.sigStr.emit(message)
Hope this helps.

How to communicate with thread in PyQt5 and wait for the result

I have a problem with my program.
I want to make new thread and start it, after calculations finishes I want to setText to the label with the result.
I also want to have a stop button so I can stop calculations if they are taking too long.
Unfortunatley it is not working right, because after I start the thread, label change is immediately and it does not work for thread to finish calculations.
Here is my sample code:
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys, random
class MyThread(QThread):
def __init__(self):
QThread.__init__(self)
self.b=1
self.result=0
def run(self):
a=0
print(self.isRunning())
while a<self.b and self.isRunning()==True:
a+=1
self.result=random.random()
class window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setObjectName("Dialog")
self.resize(367, 222)
self.label = QLabel(self)
self.label.setText("Test")
self.label.setGeometry(QRect(30, 20, 311, 121))
self.pushButton = QPushButton(self)
self.pushButton2 = QPushButton(self)
self.pushButton.setGeometry(QRect(150, 170, 75, 23))
self.pushButton2.setGeometry(QRect(150, 140, 75, 23))
self.pushButton.setText("Run")
self.pushButton2.setText("Stop")
self.pushButton.clicked.connect(self.runner)
self.pushButton2.clicked.connect(self.stopper)
self.mythread=MyThread()
def runner(self):
self.mythread.b=3000000
self.mythread.start()
self.label.setText(str(self.mythread.result))
def stopper(self):
self.mythread.terminate()
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
widget = window()
widget.show()
sys.exit(app.exec_())
OK, I solved my problem.
since self.label.setText(str(self.mythread.result)) does not update the result, as the thread has not finished, I have added one line in myThread so it looks like this now
class MyThread(QThread):
def __init__(self):
super(MyThread, self).__init__()
self.b=1
self.result=0
self.w=window
def run(self):
a=0
while a<self.b and self.isRunning()==True:
a+=1
self.result=random.random()
widget.label.setText(str(self.result))
while we can still stop the thread with stop button, the result will be set as text to label after calculations are finished.

Resources