How to use a QTimer in a separate QThread - python

I have some computationally heavy task that I want to run in a loop after every 5 seconds without blocking the main event-loop. For this, I intend to use a QTimer and a separate thread to run it. I have tried the following code but it has not worked so far:
#pyqtSlot()
def heavy_task_function():
# Sleep for 10 seconds to simulate heavy computation
time.sleep(10)
print "First Timer Fired"
if __name__ == "__main__":
app = QCoreApplication.instance()
if app is None:
app = QApplication(sys.argv)
threaded_timer = ModbusComThread(heavy_task_function)
threaded_timer.start()
sys.exit(app.exec_())
Where:
class ModbusComThread(QThread):
def __init__(self, slot_function):
QThread.__init__(self)
self.slot_function = slot_function
self.send_data_timer = None
def run(self):
print "Timer started on different thread"
self.send_data_timer = QTimer(self)
self.send_data_timer.timeout.connect(self.slot_function)
self.send_data_timer.start(5000)
def stop(self):
self.send_data_timer.stop()
The slot_function is never fired by the QTimer in threaded_timer. Is my threading architecture correct?

A QTimer needs a running event-loop. By default, QThread.run() will start a local event-loop for the thread, but if you completely override it in the way that you have done, that won't happen - so the timer events will never be processed.
In general, when you need a local event-loop you should create a worker object to do all the processing and then use moveToThread to put it in a separate thread. If not, it's perfectly okay to override QThread.run().
The demo below shows how to do this. Note that it's very important to create the timer after the thread has started, otherwise it would be created in the wrong thread and its timer-events wouldn't be processed by the thread's event-loop. It's also important that all communication between the worker thread and the main thread is done via signals, so as to ensure thread-safety. Never try to directly perform GUI operations outside the main thread, as Qt does not support that at all. For the purposes of the demo, a second timer in the main thread is used to stop all processing after a fixed interval. If there was a GUI, user intervention via a button would achieve the same thing.
Demo:
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class ModbusComWorker(QObject):
finished = pyqtSignal()
def start(self):
self._timer = QTimer(self)
self._timer.timeout.connect(self.process)
self._timer.start(2000)
def stop(self):
self._timer.stop()
self.finished.emit()
def process(self):
print('processing (thread: %r)' % QThread.currentThread())
QThread.sleep(3)
if __name__ == "__main__":
app = QCoreApplication.instance()
if app is None:
app = QApplication(sys.argv)
thread = QThread()
worker = ModbusComWorker()
worker.moveToThread(thread)
def finish():
print('shutting down...')
thread.quit()
thread.wait()
app.quit()
print('stopped')
worker.finished.connect(finish)
thread.started.connect(worker.start)
thread.start()
timer = QTimer()
timer.setSingleShot(True)
timer.timeout.connect(worker.stop)
timer.start(15000)
print('starting (thread: %r)' % QThread.currentThread())
sys.exit(app.exec_())
Output:
starting (thread: <PyQt5.QtCore.QThread object at 0x7f980d096b98>)
processing (thread: <PyQt5.QtCore.QThread object at 0x7f980d0968a0>)
processing (thread: <PyQt5.QtCore.QThread object at 0x7f980d0968a0>)
processing (thread: <PyQt5.QtCore.QThread object at 0x7f980d0968a0>)
processing (thread: <PyQt5.QtCore.QThread object at 0x7f980d0968a0>)
processing (thread: <PyQt5.QtCore.QThread object at 0x7f980d0968a0>)
shutting down...
stopped

Related

Multithreading in PyQt5 Window

I got two functions(do_work, do_work2) how do i multithread them?
If i use one Thread it works fine if i try to add one or more it wont work.
I want to have while True loops in my functions.
I already tried to change self.continue_run to : self.continue_run2 for second function but still wont work.
I hope someone can help me
import sys
from PyQt5.QtWidgets import (QWidget,
QPushButton, QApplication, QGridLayout)
from PyQt5.QtCore import QThread, QObject, pyqtSignal
import keyboard
import time
class Worker(QObject):
finished = pyqtSignal() # give worker class a finished signal
def __init__(self, parent=None):
QObject.__init__(self, parent=parent)
self.continue_run = True # provide a bool run condition for the class
def do_work(self):
while self.continue_run: # give the loop a stoppable condition
print("Thread 1")
self.finished.emit() # emit the finished signal when the loop is done
def do_work2(self):
while self.continue_run: # give the loop a stoppable condition
print("Thread 2")
self.finished.emit() # emit the finished signal when the loop is done
def stop(self):
self.continue_run = False # set the run condition to false on stop
class Gui(QWidget):
stop_signal = pyqtSignal() # make a stop signal to communicate with the worker in another thread
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# Buttons:
self.btn_start = QPushButton('Start')
self.btn_start.resize(self.btn_start.sizeHint())
self.btn_start.move(50, 50)
self.btn_stop = QPushButton('Stop')
self.btn_stop.resize(self.btn_stop.sizeHint())
self.btn_stop.move(150, 50)
# GUI title, size, etc...
self.setGeometry(300, 300, 300, 220)
self.setWindowTitle('ThreadTest')
self.layout = QGridLayout()
self.layout.addWidget(self.btn_start, 0, 0)
self.layout.addWidget(self.btn_stop, 0, 50)
self.setLayout(self.layout)
# Thread:
self.thread = QThread()
self.worker = Worker()
self.stop_signal.connect(self.worker.stop) # connect stop signal to worker stop method
self.worker.moveToThread(self.thread)
self.worker.finished.connect(self.thread.quit) # connect the workers finished signal to stop thread
self.worker.finished.connect(self.worker.deleteLater) # connect the workers finished signal to clean up worker
self.thread.finished.connect(self.thread.deleteLater) # connect threads finished signal to clean up thread
self.thread.started.connect(self.worker.do_work)
self.thread.finished.connect(self.worker.stop)
self.thread.started.connect(self.worker.do_work2)
self.thread.finished.connect(self.worker.stop)
# Start Button action:
self.btn_start.clicked.connect(self.thread.start)
# Stop Button action:
self.btn_stop.clicked.connect(self.stop_thread)
self.show()
# When stop_btn is clicked this runs. Terminates the worker and the thread.
def stop_thread(self):
self.stop_signal.emit() # emit the finished signal on stop
if __name__ == '__main__':
app = QApplication(sys.argv)
gui = Gui()
sys.exit(app.exec_())
One of the two main issues with your logic is that you've connected the finished signal to deleteLeter, and the result is that the second function would never be run anyway because you deleted both the thread and the worker.
The first important problem is that moving two functions to a thread doesn't make them run concurrently: a thread can only do one thing at a time, no matter if it's the "main" thread or not.
When the thread is started, it starts the first function the started signal is connected to, and the second will only run as soon as the first has returned (but not in your case, as explained before, since you're deleting both objects).
Consider the following example:
class WorkerTest(QtCore.QObject):
def firstFunction(self):
print('starting first function')
sleep(2)
print('finished first function')
def secondFunction(self):
print('starting second function')
sleep(2)
print('finished second function')
class Test(QtWidgets.QPushButton):
def __init__(self):
super().__init__('start')
self.worker = WorkerTest()
self.thread = QtCore.QThread()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.firstFunction)
self.thread.started.connect(self.worker.secondFunction)
self.clicked.connect(self.thread.start)
If you run the code above, you'll see that the second function will be run only as soon as the first has finished.
If you want to have two concurrent threads, create two threads.
If you want to have another thread to run after (or alternate to) the first, connect the finished signal to a function that disconnects the started from the first function and connects to the second one.

QThread Windows not responding

I wrote a GUI program in PyQt on Windows. There's some expensive operations in my program. While these operations are running, the program shows "Not Responding" in the program bar.
I think it must be this operation block the main thread to update the UI, so I write multi-threading code by QThread to test it, it still not make sense.
I wrote a small program to test it, the operation did not run in new thread at all, here is my small test code:
from PyQt5.QtCore import QThread, QObject, QCoreApplication, qDebug, QTimer
class Worker(QObject):
def on_timeout(self):
qDebug('Worker.on_timeout get called from: %s' % hex(int(QThread.currentThreadId())))
if __name__ == '__main__':
import sys
app = QCoreApplication(sys.argv)
qDebug('From main thread: %s' % hex(int(QThread.currentThreadId())))
t = QThread()
qDebug(t)
worker = Worker()
timer = QTimer()
timer.timeout.connect(worker.on_timeout)
timer.start(1000)
timer.moveToThread(t)
worker.moveToThread(t)
t.start()
app.exec_()
Here is the output:
From main thread: 0x634
Worker.on_timeout get called from: 0x634
Your program has several errors and does not produce the output that you show.
Firstly, it is not possible to pass a thread object to qDebug - the argument must be a string. If you want to print objects, use qDebug(repr(obj)) - or even better, just use print(obj).
Secondly, you cannot start a timer outside of the thread that created it. Your example makes the signal connection in the main thread, and starts the timer in the main thread as well. So worker.on_timeout will be called in the main thread. But if you connect and start the timer after moving it to the worker thread, you will get this error:
QObject::startTimer: Timers can only be used with threads started with
QThread
I think using a timer is unnecessary, and confuses your example, so it is better to leave it out altogether. Instead, you should connect the started signal of the worker thread to a run method of the worker object. To simulate a long-running operation, you can use QThread.sleep():
from PyQt5.QtCore import QThread, QObject, QCoreApplication
class Worker(QObject):
def run(self):
print('Worker called from: %#x' % int(QThread.currentThreadId()))
QThread.sleep(2)
print('Finished')
QCoreApplication.quit()
if __name__ == '__main__':
import sys
app = QCoreApplication(sys.argv)
print('From main thread: %#x' % int(QThread.currentThreadId()))
t = QThread()
worker = Worker()
worker.moveToThread(t)
t.started.connect(worker.run)
t.start()
app.exec_()
Finally, note that you should always make signal connections after moving a worker object to a thread. The reasons for this are explained in this answer.

PyQt – Signal for object created in different thread doesn't work

I’m implementing multithreading in PyQt5 and Python 3.5 by running a Worker inside a QThread. In the following sample thread2_worker (runs inside a secondary thread and) creates thread3_worker, connects thread3_worker.finished signal to thread3_worker_finished() and runs it.
When thread3_worker is done it emits finished from within its thread but the connection doesn’t work. My guess is that it has to do with thread3_worker being created or connected not in the main thread but I'd welcome any clarification.
import time
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot, QCoreApplication
class Worker(QObject):
# Generic worker.
finished = pyqtSignal()
def __init__(self, func):
super().__init__()
self.func = func
def run(self):
self.func()
self.finished.emit()
def thread_factory(func):
# Creates a Worker, a QThread and moves the Worker inside the QThread.
worker = Worker(func)
thread = QThread()
worker.moveToThread(thread)
thread.started.connect(worker.run)
# Provision graceful termination.
worker.finished.connect(thread.quit)
worker.finished.connect(worker.deleteLater)
thread.finished.connect(thread.deleteLater)
return worker, thread
def wait():
print("thread3:\t{}".format(QThread.currentThread()))
time.sleep(3)
# finished signal of thread3_worker is never received.
#pyqtSlot()
def thread3_worker_finished():
QCoreApplication.exit()
def create_thread3():
print("thread2:\t{}".format(QThread.currentThread()))
global thread3_worker, thread3
# thread3_worker runs inside thread3, and all it does is call wait().
thread3_worker, thread3 = thread_factory(wait)
thread3_worker.finished.connect(thread3_worker_finished) # FIXME Doesn't work.
thread3.start()
app = QCoreApplication([])
print("Main thread:\t{}".format(QThread.currentThread()))
thread3_worker, thread3 = None, None
# thread2_worker runs inside thread2, and creates and runs thread3_worker.
thread2_worker, thread2 = thread_factory(create_thread3)
thread2.start()
app.exec_()
Cross-thread signals require an event-loop. Your thread_factory function connects the finished signal of the worker to the quit slot of its thread. The quit slot asks the thread to exit its event-loop.
So after thread3 starts, worker2 finishes and thread2 quits. Then when the finished signal of worker3 is emitted, there is no longer an event-loop running that can process it. If you comment out the line worker.finished.connect(thread.quit), your example should work.

PyQt QThread: Destroyed while thread is still running

Despite saving a reference to the QThread as self.lightsThread, stopping the QObject self.lightsWorker then starting self.lightsThread again caused the error
QThread: Destroyed while thread is still running
After stopping self.lightsWorker, must the QThread self.lightsThread be stopped too? If not, what seems to be the problem?
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import time
class Screen(QMainWindow):
def __init__(self):
super(Screen, self).__init__()
self.initUI()
def initUI(self):
self.lightsBtn = QPushButton('Turn On')
self.lightsBtn.setCheckable(True)
self.lightsBtn.setStyleSheet("QPushButton:checked {color: white; background-color: green;}")
self.lightsBtn.clicked.connect(self.lightsBtnHandler)
self.setCentralWidget(self.lightsBtn)
def lightsBtnHandler(self):
if self.lightsBtn.isChecked():
self.startLightsThread()
else:
self.stopLightsThread()
def startLightsThread(self):
print 'start lightsThread'
self.lightsThread = QThread()
self.lightsWorker = LightsWorker()
self.lightsWorker.moveToThread(self.lightsThread)
self.lightsThread.started.connect(self.lightsWorker.work)
self.lightsThread.start()
def stopLightsThread(self):
print 'stop lightsThread'
self.lightsWorker.stop()
class LightsWorker(QObject):
signalFinished = pyqtSignal()
def __init__(self):
QObject.__init__(self)
self._mutex = QMutex()
self._running = True
#pyqtSlot()
def work(self):
while self._running:
print 'working'
time.sleep(1)
self.signalFinished.emit()
#pyqtSlot()
def stop(self):
print 'Stopping'
self._mutex.lock()
self._running = False
self._mutex.unlock()
app = QApplication(sys.argv)
window = Screen()
window.show()
sys.exit(app.exec_())
following the answer https://stackoverflow.com/a/32138213/7742341 after stopping lightWorker you should to quit from the thread and wait untill it is stopped
def stopLightsThread(self):
print('stop lightsThread')
self.lightsWorker.stop()
self.lightsThread.quit()
self.lightsThread.wait()
I had to face the same issue in C++, but the problem is the same.
The problem is that your QThread instance is deleted while the associated thread is still running. This can be really dangerous because the thread code execution is interrupted whitout any guarantee that the thread is ready to be deleted.
For example :
a thread control the execution and life time of an object (a worker)
a ressource is released in this object destructor (explicity or implicilty like when using QObject parent/child system)
since the thread execution is interrupted, the object will not be deleted
It lead to a memory leak and a ressource leak.
In your code, the worker is stopped, but not the working thread. I'm not python expert, but it also seems that your worker object is stopped but not deleted.
To properly stop your worker and thread, you should :
send a message to your worker to tell it to "stop working"
ask your thread to quit : it will post an "exit" message to the thread that will be processed after the worker execution
wait for your thread to stop
The last step is optionnal : if the thread and worker does not share ressources whith other object, you probably don't need to wait them to finish, just forget about them.
The only execption is that all your thread should be properly stopped before exiting the application : you should wait for all current running threads to stop before application exit.
For simple tasks, you should also consider using QtConcurrent framework.

How to properly terminate a QThread from a GUI application?

I tried using self.terminate() in the QThread class, and also self.thread.terminate() in the GUI class. I also tried putting self.wait() in both cases. However, there are two scenarios that happen:
1) The thread does not terminate at all, and the GUI freezes waiting for the thread to finish. Once the thread finished, the GUI unfreezes and everything is back to normal.
2) The thread indeed does terminate, but at the same time it freezes the entire application.
I also tried using self.thread.exit(). No joy.
To further clarify, I am trying to implement a user-abort button in GUI which would terminate the executing of the thread at any point in time.
Thanks in advance.
EDIT:
Here is the run() method:
def run(self):
if self.create:
print "calling create f"
self.emit(SIGNAL("disableCreate(bool)"))
self.create(self.password, self.email)
self.stop()
self.emit(SIGNAL("finished(bool)"), self.completed)
def stop(self):
#Tried the following, one by one (and all together too, I was desperate):
self.terminate()
self.quit()
self.exit()
self.stopped = True
self.terminated = True
#Neither works
And here is the GUI class' method for aborting the thread:
def on_abort_clicked(self):
self.thread = threadmodule.Thread()
#Tried the following, also one by one and altogether:
self.thread.exit()
self.thread.wait()
self.thread.quit()
self.thread.terminate()
#Again, none work
From the Qt documentation for QThread::terminate:
Warning: This function is dangerous and its use is discouraged. The
thread can be terminated at any point in its code path. Threads can be
terminated while modifying data. There is no chance for the thread to
clean up after itself, unlock any held mutexes, etc. In short, use
this function only if absolutely necessary.
It's probably a much better idea to re-think your threading strategy such that you can e.g. use QThread::quit() to signal the thread to quit cleanly, rather than trying to get the thread to terminate this way. Actually calling thread.exit() from within the thread should do that depending on how you have implemented run(). If you'd like to share the code for your thread run method that might hint as to why it doesn't work.
This is what I did:
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
stop_flag = 1
...
#=========500 ms class ===================
class Timer500msThread(QThread):
signal_500ms = pyqtSignal(str)
....
def timer500msProcess(self):
if MainWindow.stop_flag == 0 :
self.timer_500ms.stop()
#==========
#=========Main Window ===================
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
MainWindow.stop_flag=0 #this sets the flag to 0 and when the next 500ms triggers the
#the thread ends
print("Program Ending")
I had a similar problem and solved it with use of pyqtSignals and pyqtSlots.
You could create a pyqtSignal in your MainWindow-class and use the aboutToQuit-function to your QApplication instance.
Then you connect this aboutToQuit-function with another which emit a signal to the slot in your separate thread.
Then you can define a stop() function in this Thread which runs if the signal is emitted.
In this case the thread would not terminate during its work.
MainWindow:
class mainSignals(QObject):
isClosed = pyqtSignal()
class mainwindow(QMainWindow, Ui_MainWindow):
def __init__(self):
super(mainwindow, self).__init__()
self.mainSignal = mainSignals()
...
...
if __name__ = "__main__":
# This function runs if the application is closed.
# (app.aboutToQuit.connect(windowClose))
def windowClose():
window.mainSignal.isClosed.emit() # Emit the signal to the slot in (sepThread)
app = QApplication(sys.argv)
app.aboutToQuit.connect(windowClose)
window = mainwindow()
window.show()
sys.exit(app.exec())
sepThread:
class sepThread(QRunnable):
def __init__(self, parent):
super(sepThread,self).__init__()
self._parent = parent
self.mainOpen = True
self._parent.mainSignal.isClosed.connect(self.stopThread)
# If the signal was emitted by the Mainapplication
# the stopThread function runs and set the mainOpen-attribute to False
def stopThread(self):
self.mainOpen = False
def run(self):
while self.mainOpen == True:
# Do something in a loop while mainOpen-attribute is True
...
...

Categories

Resources