I'm trying to develop a software with PyQt, but I often get stuck on software crashes without debug information (only the exit code 0xC0000409). I'm using QThread, and I wrote a system like this:
class serialThreadC(QThread):
updateOutBox = QtCore.pyqtSignal(str)
updateStatus = QtCore.pyqtSignal(int)
def __init__(self):
super(serialThreadC, self).__init__()
self.ser = False
self.state = 0
self.serialEnabled = False
def run(self):
while True:
if self.state == -3 or self.state == -2:
if self.SerialEnabled:
self.updatePB(20)
elif self.state == 0:
if self.serialEnabled:
self.updatePB(20)
def ConnDisconn(self):
self.serialEnabled = not self.serialEnabled
def updatePB(self, stat):
self.state = stat
self.updateStatus.emit(self.state)
serialThread = serialThreadC()
serialThread.start()
## sw is a QDialog already loaded
serialThread.updateOutBox.connect(sw.updateOutBox)
serialThread.updateStatus.connect(sw.updateStatus)
sw.PB_ConnDisconn.clicked.connect(serialThread.ConnDisconn)
I have crashes when I read/write serialEnabled in run() or in ConnDisconn(). I know that PyQt is not thread-safe and that a wrong handling of variables gives crashes of my type, but I can't understand what is wrong with my code. My idea (maybe wrong) is that all serialThread methods are executed on the same thread, also if they are connected to a gui (main thread). Is that wrong? In the same way, I emit events from serialThread and I connected them to the GUI, but that never gave me problems.
Can you see the mistake I made? Is there a way to debug the code if there is a crash without other infos? (I use PyCharm 2017.1.3).
PyQt is thread-safe to the same extent that Qt is thread-safe. The Qt docs will tell you which parts of their API are guaranteed to be so, and under what circumstances.
Cross-thread signals are thread-safe, so calling the updatePB method in your example is okay. Your ConnDisconn method is not thread-safe, but that has got nothing to do with PyQt or Qt - it's just a consequence of how you wrote it. The serialEnabled attribute could be read/written by two threads simultaneously, so the behaviour is strictly undefined. A thread-safe way of writing this would be to use a mutex, like so:
class serialThreadC(QThread):
updateOutBox = QtCore.pyqtSignal(str)
updateStatus = QtCore.pyqtSignal(int)
def __init__(self):
super(serialThreadC, self).__init__()
self.ser = False
self.state = 0
self._mutex = QMutex()
self.serialEnabled = False
def ConnDisconn(self):
self._mutex.lock()
self.serialEnabled = not self.serialEnabled
self._mutex.unlock()
def run(self):
while True:
if self.state == -3 or self.state == -2:
self._mutex.lock()
if self.serialEnabled:
self.updatePB(20)
self._mutex.unlock()
elif self.state == 0:
self._mutex.lock()
if self.serialEnabled:
self.updatePB(20)
self._mutex.unlock()
(NB: if you're using any kind of IDE or debugger, and you are getting unexpected errors or crashes, your first step in diagnosing the problem should always be to test the code in a standard console. Quite often, the IDE or debugger itself can be the cause of the problem, or may mask error messages comming either from Python or from underlying libraries, such as Qt).
I set all float variables to int values and it worked
before
var1 = 10.0
var2 = 26.0
after
var1 = 10
var2 = 26
Related
I'm developing a Kodi add-on that uses a custom overlay. It's almost completely working now, except for one runtime error I get every time I start a video, after reaching 5 seconds in the video ("Error Contents: Control does not exist in window"). Here's the code for addon.py below:
import xbmc, xbmcaddon, xbmcgui, os
ADDON = xbmcaddon.Addon()
addonpath = ADDON.getAddonInfo('path')
class OverlayBackground(object):
def __init__(self):
self.showing = False
self.window = xbmcgui.Window()
origin_x = 0
origin_y = 0
window_w = self.window.getWidth()
window_h = self.window.getHeight()
self._background = xbmcgui.ControlImage(origin_x, origin_y, window_w, window_h, os.path.join(addonpath,"resources","skins","default","media","background.png"))
def show(self):
self.showing=True
self.window.addControl(self._background)
def hide(self):
self.showing=False
self.window.removeControl(self._background)
def _close(self):
if self.showing:
self.hide()
else:
pass
try:
self.window.clearProperties()
except: pass
if (__name__ == '__main__'):
monitor = xbmc.Monitor()
while not monitor.abortRequested():
if xbmc.Player().isPlaying():
theOverlay = OverlayBackground()
if(xbmc.Player().getTime() < 5):
theOverlay.show()
else:
theOverlay.hide()
xbmc.sleep(1000)
The error came from "self.window.removeControl(self._background)", as mentioned in this paste bin. It seems odd, because I'm sure that the control was added prior to running this command, so I'm not sure what's causing this command to crash the add-on.
I tried asking over in the Kodi forums for help, and was told that I need to get the ID of the player that I'm controlling (and linked to the documentation for Control), but I'm not sure how to implement that, even after researching the documentation extensively.
I would highly appreciate any clarification on this. Thank you so much in advance!
you need to check if the overlay already removed
def show(self):
if not self.showing:
self.showing=True
self.window.addControl(self._background)
def hide(self):
if self.showing:
self.showing=False
self.window.removeControl(self._background)
I got a project that listens to the microphone if I ever press start. I am so new to pyqt5 so I don't really know Qthreading that much, plus most of the examples are progress bars and for loops. I want that when it presses start it runs the while loop of recording audio for 10 seconds, identifying it, and doing again. And that it only stops when I press stop, a variable is now False. Now this is what I came up with. I am sorry if it looks dumb because I don't really know that much from Qthread. I just want to be able to run this one in particular.
class AvisoWindow(QtWidgets.QMainWindow, Aviso_Main.Ui_MainWindow):
def __init__(self, parent=None):
super(AvisoWindow, self).__init__(parent)
self.setupUi(self)
self.is_Played = True
self.start_btn.clicked.connect(self.startClicked)
self.stop_btn.clicked.connect(self.stopClicked)
def startClicked(self):
while self.is_Played == True:
listen()
time.sleep(0.01)
def stopClicked(self):
self.is_Played = False
self.finished.emit()
if __name__ == "__main__":
app = QApplication(sys.argv)
form = AvisoWindow()
threading.Thread(target=form.__init__(), daemon=True).start()
form.show()
app.exec_()
First of all, nothing related to the UI shall ever be run or accessed in an external thread. Blocking functions and while loops (unless it's guaranteed that they exit almost instantly) should also be avoided, as they block the event loop preventing proper redrawing of the UI and user interaction.
Even assuming that directly using threads with UI was possible, your attempt has two important problems:
__init__() should never be called externally (and, even in that case, it expects at least the instance as argument);
the target argument of Thread expects a callable (a reference to a function), but you're already calling the method (using the parentheses), so it would use the returned value of that called function (which is usually None for an init);
In order to use threads that have to react to an UI environment, in Qt you should create a subclass of QThread that acts as your "worker".
class Worker(QThread):
result = pyqtSignal(str)
def run(self):
self.keepRunning = True
while self.keepRunning:
res = someFunctionThatReturnsAString()
self.result.emit(res)
time.sleep(0.01)
def stop(self):
self.keepRunning = False
class AvisoWindow(QtWidgets.QMainWindow, Aviso_Main.Ui_MainWindow):
def __init__(self, parent=None):
super(AvisoWindow, self).__init__(parent)
self.setupUi(self)
self.worker = Worker()
self.worker.result.connect(self.resultReceived)
self.start_btn.clicked.connect(self.startClicked)
self.stop_btn.clicked.connect(self.stopClicked)
def startClicked(self):
if not self.worker.isRunning():
self.worker.start()
def stopClicked(self):
self.worker.stop()
def resultReceived(self, result):
print(result)
I'm in the process of writing my very first GUI application with PyQt4 and I've come upon a question that seems very basic, yet I don't seem to find a good answer:
I'm using a thread to continuously perform a repeated task without blocking the main window. The thread needs some information from the main window (e.g. the current value of a spinbox) that can also change during the runtime of the thread. In this situation, what is the proper way to share such data between the main window and the thread?
Naively, I could come up with the following possibilities:
Pass a reference to the host window to the thread and use this to retrieve the current value of the variable in question (see example below).
Keep a copy of the variable in the thread and keep it synchronized with the main window by emitting signals whenever it changes.
Use global variables.
All three options would most likely work for my particular use case (though 2 would be a bit complicated), but I have a feeling there should be a better/more Pythonic/more Qt-like way.
Here is a minimum working example illustrating what I want to do, in this case using option 1:
from PyQt4 import QtGui, QtCore
import time, sys
class MainWindow(QtGui.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.layout = QtGui.QVBoxLayout(self)
self.spinbox = QtGui.QSpinBox(self)
self.spinbox.setValue(1)
self.layout.addWidget(self.spinbox)
self.output = QtGui.QLCDNumber(self)
self.layout.addWidget(self.output)
self.worker = Worker(self)
self.connect(self.worker, QtCore.SIGNAL('beep'), self.update)
self.worker.start()
def update(self, number):
self.output.display(number)
class Worker(QtCore.QThread):
def __init__(self, host_window):
super(Worker, self).__init__()
self.host = host_window
self.running = False
def run(self):
self.running = True
i = 0
while self.running:
i += 1
self.emit(QtCore.SIGNAL('beep'), i)
sleep_time = self.host.spinbox.value()
time.sleep(sleep_time)
def stop(self):
self.running = False
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
PS: Since I'm completely unexperienced with PyQt it's not unlikely that there are other problems with the code or the question is unclear. In this case, please feel free to comment or edit the question.
Widgets are not thread safe, see Threads and QObjects:
Although QObject is reentrant, the GUI classes, notably QWidget and
all its subclasses, are not reentrant. They can only be used from the
main thread.
And see more definitions here: Reentrancy and Thread-Safety
You should only use widgets in the main thread, and use signal and slots to communicate with other threads.
I don't think a global variable would work, but I honestly don't know why.
How to use signals in this example:
#in main
self.worker = Worker(self.spinbox.value())
self.worker.beep.connect(self.update)
self.spinbox.valueChanged.connect(self.worker.update_value)
class Worker(QtCore.QThread):
beep=QtCore.pyqtSignal(int)
def __init__(self,sleep_time):
super(Worker, self).__init__()
self.running = False
self.sleep_time=sleep_time
def run(self):
self.running = True
i = 0
while self.running:
i += 1
self.beep.emit(i)
time.sleep(self.sleep_time)
def stop(self):
self.running = False
def update_value(self,value):
self.sleep_time=value
NB: I use the new style signal and slots
I am trying to understand how to use signaling from a Qthread back to the Gui interface that started.
Setup: I have a process (a simulation) that needs to run almost indefinitely (or at least for very long stretches of time)., While it runs, it carries out various computations, amd some of the results must be sent back to the GUI, which will display them appropriately in real time.
I am using PyQt for the GUI. I originally tried using python's threading module, then switched to QThreads after reading several posts both here on SO and elsewhere.
According to this post on the Qt Blog You're doing it wrong, the preferred way to use QThread is by creating a QObject and then moving it to a Qthread. So I followed the advice inBackground thread with QThread in PyQt"> this SO question and tried a simple test app (code below): it opens up a simple GUI, let you start the background process, and it issupposed to update the step value in a spinbox.
But it does not work. The GUI is never updated. What am I doing wrong?
import time, sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class SimulRunner(QObject):
'Object managing the simulation'
stepIncreased = pyqtSignal(int, name = 'stepIncreased')
def __init__(self):
super(SimulRunner, self).__init__()
self._step = 0
self._isRunning = True
self._maxSteps = 20
def longRunning(self):
while self._step < self._maxSteps and self._isRunning == True:
self._step += 1
self.stepIncreased.emit(self._step)
time.sleep(0.1)
def stop(self):
self._isRunning = False
class SimulationUi(QDialog):
'PyQt interface'
def __init__(self):
super(SimulationUi, self).__init__()
self.goButton = QPushButton('Go')
self.stopButton = QPushButton('Stop')
self.currentStep = QSpinBox()
self.layout = QHBoxLayout()
self.layout.addWidget(self.goButton)
self.layout.addWidget(self.stopButton)
self.layout.addWidget(self.currentStep)
self.setLayout(self.layout)
self.simulRunner = SimulRunner()
self.simulThread = QThread()
self.simulRunner.moveToThread(self.simulThread)
self.simulRunner.stepIncreased.connect(self.currentStep.setValue)
self.connect(self.stopButton, SIGNAL('clicked()'), self.simulRunner.stop)
self.connect(self.goButton, SIGNAL('clicked()'), self.simulThread.start)
self.connect(self.simulRunner,SIGNAL('stepIncreased'), self.currentStep.setValue)
if __name__ == '__main__':
app = QApplication(sys.argv)
simul = SimulationUi()
simul.show()
sys.exit(app.exec_())
The problem here is simple: your SimulRunner never gets sent a signal that causes it to start its work. One way of doing that would be to connect it to the started signal of the Thread.
Also, in python you should use the new-style way of connecting signals:
...
self.simulRunner = SimulRunner()
self.simulThread = QThread()
self.simulRunner.moveToThread(self.simulThread)
self.simulRunner.stepIncreased.connect(self.currentStep.setValue)
self.stopButton.clicked.connect(self.simulRunner.stop)
self.goButton.clicked.connect(self.simulThread.start)
# start the execution loop with the thread:
self.simulThread.started.connect(self.simulRunner.longRunning)
...
Here is the code sample:
class RunGui (QtGui.QMainWindow)
def __init__(self, parent=None):
...
QtCore.Qobject.connect(self.ui.actionNew, QtCore.SIGNAL("triggered()"), self.new_select)
...
def normal_output_written(self, qprocess):
self.ui.text_edit.append("caught outputReady signal") #works
self.ui.text_edit.append(str(qprocess.readAllStandardOutput())) # doesn't work
def new_select(self):
...
dialog_np = NewProjectDialog()
dialog_np.exec_()
if dialog_np.is_OK:
section = dialog_np.get_section()
project = dialog_np.get_project()
...
np = NewProject()
np.outputReady.connect(lambda: self.normal_output_written(np.qprocess))
np.errorReady.connect(lambda: self.error_output_written(np.qprocess))
np.inputNeeded.connect(lambda: self.input_from_line_edit(np.qprocess))
np.params = partial(np.create_new_project, section, project, otherargs)
np.start()
class NewProject(QtCore.QThread):
outputReady = QtCore.pyqtSignal(object)
errorReady = QtCore.pyqtSignal(object)
inputNeeded = QtCore.pyqtSignal(object)
params = None
message = ""
def __init__(self):
super(NewProject, self).__init__()
self.qprocess = QtCore.QProcess()
self.qprocess.moveToThread(self)
self._inputQueue = Queue()
def run(self):
self.params()
def create_new_project(self, section, project, otherargs):
...
# PyDev for some reason skips the breakpoints inside the thread
self.qprocess.start(command)
self.qprocess.waitForReadyRead()
self.outputReady.emit(self.qprocess) # works - I'm getting signal in RunGui.normal_output_written()
print(str(self.qprocess.readAllStandardOutput())) # prints empty line
.... # other actions inside the method requiring "command" to finish properly.
The idea is beaten to death - get the GUI to run scripts and communicate with the processes. The challenge in this particular example is that the script started in QProcess as command runs an app, that requires user input (confirmation) along the way. Therefore I have to be able to start the script, get all output and parse it, wait for the question to appear in the output and then communicate back the answer, allow it to finish and only then to proceed further with other actions inside create_new_project()
I don't know if this will fix your overall issue, but there are a few design issues I see here.
You are passing around the qprocess between threads instead of just emitting your custom signals with the results of the qprocess
You are using class-level attributes that should probably be instance attributes
Technically you don't even need the QProcess, since you are running it in your thread and actively using blocking calls. It could easily be a subprocess.Popen...but anyways, I might suggest changes like this:
class RunGui (QtGui.QMainWindow)
...
def normal_output_written(self, msg):
self.ui.text_edit.append(msg)
def new_select(self):
...
np = NewProject()
np.outputReady.connect(self.normal_output_written)
np.params = partial(np.create_new_project, section, project, otherargs)
np.start()
class NewProject(QtCore.QThread):
outputReady = QtCore.pyqtSignal(object)
errorReady = QtCore.pyqtSignal(object)
inputNeeded = QtCore.pyqtSignal(object)
def __init__(self):
super(NewProject, self).__init__()
self._inputQueue = Queue()
self.params = None
def run(self):
self.params()
def create_new_project(self, section, project, otherargs):
...
qprocess = QtCore.QProcess()
qprocess.start(command)
if not qprocess.waitForStarted():
# handle a failed command here
return
if not qprocess.waitForReadyRead():
# handle a timeout or error here
return
msg = str(self.qprocess.readAllStandardOutput())
self.outputReady.emit(msg)
Don't pass around the QProcess. Just emit the data. And create it from within the threads method so that it is automatically owned by that thread. Your outside classes should really not have any knowledge of that QProcess object. It doesn't even need to be a member attribute since its only needed during the operation.
Also make sure you are properly checking that your command both successfully started, and is running and outputting data.
Update
To clarify some problems you might be having (per the comments), I wanted to suggest that QProcess might not be the best option if you need to have interactive control with processes that expect periodic user input. It should work find for running scripts that just produce output from start to finish, though really using subprocess would be much easier. For scripts that need user input over time, your best bet may be to use pexpect. It allows you to spawn a process, and then watch for various patterns that you know will indicate the need for input:
foo.py
import time
i = raw_input("Please enter something: ")
print "Output:", i
time.sleep(.1)
print "Another line"
time.sleep(.1)
print "Done"
test.py
import pexpect
import time
child = pexpect.spawn("python foo.py")
child.setecho(False)
ret = -1
while ret < 0:
time.sleep(.05)
ret = child.expect("Please enter something: ")
child.sendline('FOO')
while True:
line = child.readline()
if not line:
break
print line.strip()
# Output: FOO
# Another line
# Done