Threading a task beside a GUI PyQT4 - python

So i am trying to run a PyQT GUI while another functions is gathering information in the background. If Information is found the GUI should update itself.
I am new in Threading so i googled a lot and found some good HowTo's although it does not work as it should.
when i run the program it just ends itself after 3 s.
Maybe you see some major mistake ive done.
Here is the basic code i am trying to get to run
class scan_Thread(QThread):
def __init__(self, samp_rate, band, speed, ppm, gain, args, prn):
QThread.__init__(self)
self.samp_rate=samp_rate
self.band=band
self.speed=speed
self.ppm=ppm
self.gain=gain
self.args=args
self.prn=prn
def __del__(self):
self.wait()
def run(self):
do_scan(self.samp_rate, self.band, self.speed,
self.ppm, self.gain, self.args, self.prn)
def start_gui():
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
#app.exec_()
#sys.exit()
def main(options = None):
def printfunc(found_list):
for info in sorted(found_list):
print info
get_thread = scan_Thread(options.samp_rate, options.band, options.speed,
options.ppm, options.gain, options.args, printfunc)
get_thread.start()
start_gui()
Thanks!

Many of the objects of the Qt classes and therefore of PyQt need to start some object of type Application (QCoreApplication, QtGuiApplication or QApplication), but only one of these objects must exist.
In your particular case QThread needs it. The previous classes are responsible for generating the necessary loops.
So you should modify your code to the following:
def main(options = None):
app = QtGui.QApplication(sys.argv) // before creating any PyQt object.
def printfunc(found_list):
for info in sorted(found_list):
print info
get_thread = scan_Thread(options.samp_rate, options.band, options.speed,
options.ppm, options.gain, options.args, printfunc)
get_thread.start()
window = Window()
window.show()
sys.exit(app.exec_())

Related

PyQt QTimer problem QTimer can only be used with threads started with QThread

I had an annoying problem with my PyQt app, but I fixed it. I present it here so other newbies can avoid making the same mistake.
My PyQt app had a class derived from QGraphicsScene. The scene adds about a hundred QPixMaps to itself.
Trouble was, when the program exited, I got about a hundred of the following error messages:
QObject::startTimer: QTimer can only be used with threads started with QThread
and this is my code:
from PyQt5 import QtCore, QtGui
class ModelHexMap(QtGui.QGraphicsScene):
def __init__(self, tilePath, mapPath, parent=None):
QtGui.QGraphicsScene.__init__(self, parent)
self.commonInit(tilePath, mapPath)
def commonInit(tilePath, mapPath):
# make calls to self.addPixmap()
class MyForm(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
# the following causes the error
self.ModelHexMap = ModelHexMap("game.til", "game.map")
# this is the correct way
self.ModelHexMap = ModelHexMap("game.til", "game.map", self)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = MyForm()
myapp.show()
sys.exit(app.exec_())
how can i fix it?
I don't put any effort into chasing exit crashes anymore--just use pg.exit() and be done with it.
(but if you do happen to find a bug in pyqtgraph, don't hesitate to open an issue on github)

Python crashes when running a log streamer using Qt

Goal
I have a process that logs on a file (realtime.log) while running and I want to print every new line of that file in my application in realtime. In other words I want to redirect the output from the process to the GUI. This means that I have two different processes running: the "engine" and the GUI.
I have already achieved this by using Tkinter but since I have to make a more complex, professional and good looking GUI I decided to switch to Qt for Python (PySide2).
Problem
Python often crashes when I launch the GUI with the error message: Python has stopped working. The window starts printing the lines and at some point it stops working.
After many attempts and searches I got to a point where the program only crashes if I click on the GUI window. Moreover, the program doesn't crash suddenly but it crashes at the end of the engine's execution.
Environment
Windows 10
Python 3.6.5
PySide2 5.12.6
Code
Note that this is a simplified version.
datalog_path = "realtime.log"
def get_array_from_file(file_path):
try:
with open(file_path, 'r') as file:
lines = file.readlines()
return lines
except:
print('error in file access')
class Streamer(QRunnable):
def __init__(self, stream, old_array, edit):
super().__init__()
self.stream = stream
self.old_array = old_array
self.edit = edit
def run(self):
try:
while self.stream:
array_file = get_array_from_file(datalog_path)
if len(array_file) != len(self.old_array):
for line in array_file[len(self.old_array) - 1:len(array_file)]:
self.edit.append(line)
# print(line)
self.old_array.append(line)
except:
print('problem in streaming funct')
class Window(QMainWindow):
def __init__(self):
super().__init__()
layout = QVBoxLayout()
self.setWindowTitle("DATALOG")
self.thread_pool = QThreadPool()
self.edit = QTextEdit()
self.stream = True
self.old_array = get_array_from_file(datalog_path)
self.streamer = Streamer(self.stream, self.old_array, self.edit)
self.thread_pool.start(self.streamer)
window = QWidget()
layout.addWidget(self.edit)
window.setLayout(layout)
self.setCentralWidget(window)
def closeEvent(self, event):
self.stream = False
event.accept()
if __name__ == '__main__':
app = QApplication(sys.argv)
win = Window()
win.show()
app.exec_()
The #hyde answer points out explains the reason for the problem but its solution is not applicable in PySide2 (in PyQt5 a small modification would have to be made, see this), an alternative is to create a QObject that has the signals:
class Signaller(QtCore.QObject):
textChanged = Signal(str)
class Streamer(QRunnable):
def __init__(self, stream, old_array):
super().__init__()
self.stream = stream
self.old_array = old_array
self.signaller = Signaller()
def run(self):
try:
while self.stream:
array_file = get_array_from_file(datalog_path)
if len(array_file) != len(self.old_array):
for line in array_file[len(self.old_array) - 1:len(array_file)]:
self.signaller.textChanged.emit(line)
# print(line)
self.old_array.append(line)
except:
print('problem in streaming funct')
self.stream = True
self.old_array = get_array_from_file(datalog_path)
self.streamer = Streamer(self.stream, self.old_array)
self.streamer.signaller.textChanged.connect(self.edit.append)
self.thread_pool.start(self.streamer)
While I'm not too familiar with Python Qt, issue is probably, that you use a GUI object edit from a different thread. This is not allowed, the GUI part must all run in the same (main) thread!
To fix this, you need to have some other way for the thread to communicate UI changes. Since your QRunnable is not a QObject, you can't just emit a signal, but you can use QMetaObject::invokeMethod on it's invokable methods. Please let me know if this works directly:
# self.edit.append(line) # can't do this from a thread!
# instead, invoke append through GUI thread event loop
QtCore.QMetaObject.invokeMethod(self.edit,
'append',
QtCore.Qt.QueuedConnection,
QtCore.QGenericArgument('QString', line)

Properly update restart GUI from another thread PyQT

So i have an process which has its own thread and writes(appends) to an output.txt. Also i have a GUI which reads from this output.txt. I want to update the GUI whenever there is new content in the Output.txt.
right now i do this by quiting and re-exec() the GUI. This works but looks awfull.
class _do_scan(QtCore.QThread):
trigger = QtCore.pyqtSignal()
def __init__(self):
QThread.__init__(self)
def run(self):
self.do_scan()
def do_scan():
#when found smth and written to output.txt
self.trigger.emit()
def main(options = None):
app = QtGui.QApplication(sys.argv)
scan_thread = _do_scan()
scan_thread.start()
scan_thread.trigger.connect(lambda: app.quit())
while scan_thread.isRunning():
window = Window()
window.show()
app.exec_()
window = Window()
window.show()
sys.exit(app.exec_())
Any idea how i can manage it better?
i tried to use
scan_thread.trigger.connect(lambda: window.update())
scan_thread.trigger.connect(lambda: window.repaint())
but none worked
Thanks in advance

Change Button Color in Qt Thread Python

I need to change the color of QPushButton, but an error occurred: "AttributeError: type object 'ProyectoTFM' has no attribute 'ui'".
I don't know hoy to acced to a ui variable from my thread.
This is my code:
import sys
import OpenOPC
import time
import threading
from proyectoQt import *
def actualizarDatosOPC():
while 1:
time.sleep(5)
if(itemsOPC[15])[1]!=0:
#Error on next line
ProyectoTFM.ui.AP08Button.setStyleSheet("background-color: red")
return
class ProyectoTFM(QtGui.QMainWindow):
def __init__(self,parent=None):
QtGui.QMainWindow.__init__(self,parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.startTheThread()
print('Init')
def startTheThread(self):
threadQt = threading.Thread(target = actualizarDatosOPC)
threadQt.start()
def clienteOPC():
opc=OpenOPC.client()
opc.connect('Kepware.KEPServerEX.V6')
global itemsOPC
while 1:
itemsOPC = opc.read(opc.list('PLC.PLC.TAGS'))
time.sleep(5)
return
threads = list()
threadOPC = threading.Thread(target=clienteOPC)
threads.append(threadOPC)
threadOPC.start()
time.sleep(5)
if __name__== "__main__":
app=QtGui.QApplication(sys.argv)
myapp = ProyectoTFM()
myapp.show()
sys.exit(app.exec_())
threadOPC.__delete()
Sorry for my English and thanks.
It is not correct to modify the view from a different thread to the main one, a way to solve the problem without using QThread is to create a signal that connects to some slot that changes the color of the button. To be able to emit the signal from the new thread we must pass the object to him through the parameter args.
def actualizarDatosOPC(obj):
while 1:
time.sleep(5)
if(itemsOPC[15])[1]!=0:
#Error on next line
obj.sendChangeColor.emit()
return
class ProyectoTFM(QtGui.QMainWindow):
sendChangeColor = QtCore.pyqtSignal()
def __init__(self,parent=None):
QtGui.QMainWindow.__init__(self,parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.startTheThread()
print('Init')
self.sendChangeColor.connect(lambda: self.ui.AP08Button.setStyleSheet("background-color: red"))
def startTheThread(self):
threadQt = threading.Thread(target = actualizarDatosOPC, args=(self,))
threadQt.start()
Even if you got this to work, you can't modify the UI from a thread directly.
A few things:
You never actually pass the UI to the function actualizarDatosOPC
so it doesn't know it exists.
Is there any reason you can't use PyQt's built in threading tools? If you are going to use PyQt it might make sense to buy into the whole framework.
def startTheThread(self):
self.threadQt = QThread()
d = actualizarDatosOPC(self)
d.moveToThread(self.threadQt)
self.threadQt.start()
def actualizarDatosOPC(widget):
.... widget.AP08Button.setStyleSheet("background-color: red")
If you do choose to go this route, I'd take a look at this thread which has a good example:
How to use QThread correctly in pyqt with moveToThread()?
Additionally, while the way you initialize your Window works, this is the more standard way to do it:
class ProyectoTFM(QMainWindow, Ui_MainWindow):
def __init__(self, parent):
# General Init Stuff
super(Login, self).__init__(parent)
self.setupUi(self)
After that, whenever you want to refer to something in the UI all you need to do is refer to self._____. For example, if you have a button named buttonA, self.buttonA would be the appropriate reference.
Edit:
As mentioned in another answer, the proper way to actually change the button color would be to emit a trigger that to the main thread which could then respond by changing the button color.

Multithreading with pyqt - Can't get the separate threads running at the same time?

I am trying to get a PyQT GUI running ontop of my python application and I have tried to get it separated into 2 threads so the GUI would be responsive while my main running loop goes, but I have not been able to get it going. Maybe I am misunderstanding it. Here is what I've tried:
My Window and Worker thread are defined as follows:
class Window(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self, parent)
self.thread = Worker()
start = QPushButton("Start", self)
QObject.connect(start, SIGNAL("clicked()"), MAIN_WORLD.begin)
hbox = QVBoxLayout(self)
hbox.addStretch(4)
class Worker(QThread):
def __init__(self, parent = None):
QThread.__init__(self, parent)
if __name__ == '__main__':
MAIN_WORLD = World()
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
which seems to follow very closely to online examples. My World class is running a loop that is infinite once the user clicks "Start" until it's clicked again. Here is part of the definition of it.
class World(QThread):
def __init__(self, parent = None):
QThread.__init__(self, parent)
self.currentlyRunning = False
//snip
def begin(self):
if self.currentlyRunning:
self.currentlyRunning = False
else:
self.currentlyRunning = True
self.MethodThatDoesUsefulStuff()
edit: I have noticed that I'm not really "using" my worker thread. How do I create my world thread as a worker thread?
After looking more closely at your code, you have MAIN_WORLD started before QApplication, which isn't what you want.
You want to do something like this:
if __name__ == '__main__':
app = QApplication(sys.argv)
sys.exit(app.exec_())
And, in your Window class:
class Window(QWidget):
def __init__(self, *args):
self.world_thread = World();
# ...
The above will allow the main thread to control the gui and allow the worker threads to work in the background.
Granted that is PyQt and not PySide, but:
Don't subclass QThread, subclass QObject (see http://blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/).
Then the basic workflow is to create a new thread, move your worker into the thread, start the thread and your worker. Your problem may be that you never actually start your new thread, initialization won't do that - then both your GUI and your worker are running the same thread, as others have commented. The GIL doesn't really enter into the picture. Check out the QThread docs: http://doc.qt.io/qt-4.8/qthread.html.

Categories