Display busy progressBar for long process, no thread - python

I'm writing a PyQt GUI application, and I would like to display a busy progress bar for one of my function, which takes time to finish. Here is the code:
self.progress = QtGui.QProgressDialog("Canceling...", None, 0, 0, self)
self.progress.setWindowTitle("Canceling refresh")
self.progress.show()
timer = QtCore.QTimer()
timer.singleShot(0, self.loadNotifications)
while timer.isActive():
app.processEvents()
For now, I tried something with a QTimer, but it doesn't work. The CPU-bound function is loadNotifications.
I would like to start it in a sort of a thread, and while it's running, update the QProgressBar.
I could of course create a QThread class around the function loadNotifications, but it's a bit overkill for what I want to do: simply display a smooth progressBar while the function is running.
Do you have any idea ?

I've no experience with PyQT, but in tkinter, I usually accomplish this by making my slow function periodically call some update_progbar function passed to it. This update_progbar function then updates the progress bar in the same thread as the slow function (which is therefore temporarily on hold).
Rudimentary:
def update_progbar(prog):
print(prog)
def slow_function(prog_func):
for i in range(100000):
if i%1000 == 0:
prog_func(i / 100000.0)
do_something()

Related

Pyqt5 qprogressbar different behavior with QSplashScreen and QWidget [duplicate]

I can't use time.sleep in my pyqt application because that freezes the GUI thread, so the GUI will be completely frozen during this time.I have been looking for a way to handle this.
I tried to use QTimer, but it seemed like they need to be linked to another function? Like wait ten seconds then run some function. Is there a way to just have it wait then continue with the current function?
def num(self):
for i in range(1,999):
print i
#Add some sleep here
def testSleep(self):
QtCore.QTimer.singleShot(2000, self.num)
Actually i was looking for time.sleep alternative to use in pyqt without using any thread concepts.
And the solution i figured out is:
from PyQt4 import QtTest
QtTest.QTest.qWait(msecs)
This works similar to time.sleep making GUI responsive.
Thankyou for your answers.
Maybe it could be done better but you can always use singleShot to run function with delay, and lambda to run function with argument.
import sys
from PyQt4 import QtGui, QtCore
#def num(self, i):
def num(i):
print i
i += 1
if i < 999:
# run again after 2000ms with argument
QtCore.QTimer.singleShot(2000, lambda:num(i))
#QtCore.QTimer.singleShot(2000, lambda:self.num(i))
app = QtGui.QApplication(sys.argv)
# run first time with start argument
num(1)
#QtCore.QTimer.singleShot(2000, lambda:num(1))
sys.exit(app.exec_())
Another option would be to process Qt events while waiting:
def num():
for i in range(1, 999):
print(i)
# Sleep five seconds in total
for _ in range(5 * 10):
# Process events between short sleep periods
QtWidgets.QApplication.processEvents()
time.sleep(0.1)
You cannot use time.sleep in the pyqt main event loop as it would stop the GUI event loop from responding.
A solution in pyqt could look like this, using QTimer
import sys
from PyQt4 import QtGui, QtCore
application = QtGui.QApplication(sys.argv)
i=0
timer = QtCore.QTimer()
def num():
global i, timer
if i <999:
print ( i )
i += 1
else:
timer.stop()
timer.timeout.connect(num)
timer.start(2000)
sys.exit(application.exec_())
I believe you are asking how to keep the GUI responsive if num() takes several seconds to run? You have two options:
if num() consists of many small chunks of "work", you can call application.processEvents() between the chunks, this will allow the GUI to respond to events. An easy situation to deal with is when most of the num() time is spent in a loop, then at the start or end of each iteration, call application.processEvents(). In your real application, if you don't have access to application, import qApp from PyQt4.
the better approach is to execute num() in a separate thread. There are many examples of this on SO (like this one). One way of doing that is
instantiate a QThread,
define a class (say NumberCruncher) that derives from QObject and defines num(self) and defines a signal 'done' emitted by num() before returning
call numberCruncher.moveToThread(thread)
connect the thread started signal to num
start the thread

How to show timer in a Quiz Application using pyqt5 python [duplicate]

I can't use time.sleep in my pyqt application because that freezes the GUI thread, so the GUI will be completely frozen during this time.I have been looking for a way to handle this.
I tried to use QTimer, but it seemed like they need to be linked to another function? Like wait ten seconds then run some function. Is there a way to just have it wait then continue with the current function?
def num(self):
for i in range(1,999):
print i
#Add some sleep here
def testSleep(self):
QtCore.QTimer.singleShot(2000, self.num)
Actually i was looking for time.sleep alternative to use in pyqt without using any thread concepts.
And the solution i figured out is:
from PyQt4 import QtTest
QtTest.QTest.qWait(msecs)
This works similar to time.sleep making GUI responsive.
Thankyou for your answers.
Maybe it could be done better but you can always use singleShot to run function with delay, and lambda to run function with argument.
import sys
from PyQt4 import QtGui, QtCore
#def num(self, i):
def num(i):
print i
i += 1
if i < 999:
# run again after 2000ms with argument
QtCore.QTimer.singleShot(2000, lambda:num(i))
#QtCore.QTimer.singleShot(2000, lambda:self.num(i))
app = QtGui.QApplication(sys.argv)
# run first time with start argument
num(1)
#QtCore.QTimer.singleShot(2000, lambda:num(1))
sys.exit(app.exec_())
Another option would be to process Qt events while waiting:
def num():
for i in range(1, 999):
print(i)
# Sleep five seconds in total
for _ in range(5 * 10):
# Process events between short sleep periods
QtWidgets.QApplication.processEvents()
time.sleep(0.1)
You cannot use time.sleep in the pyqt main event loop as it would stop the GUI event loop from responding.
A solution in pyqt could look like this, using QTimer
import sys
from PyQt4 import QtGui, QtCore
application = QtGui.QApplication(sys.argv)
i=0
timer = QtCore.QTimer()
def num():
global i, timer
if i <999:
print ( i )
i += 1
else:
timer.stop()
timer.timeout.connect(num)
timer.start(2000)
sys.exit(application.exec_())
I believe you are asking how to keep the GUI responsive if num() takes several seconds to run? You have two options:
if num() consists of many small chunks of "work", you can call application.processEvents() between the chunks, this will allow the GUI to respond to events. An easy situation to deal with is when most of the num() time is spent in a loop, then at the start or end of each iteration, call application.processEvents(). In your real application, if you don't have access to application, import qApp from PyQt4.
the better approach is to execute num() in a separate thread. There are many examples of this on SO (like this one). One way of doing that is
instantiate a QThread,
define a class (say NumberCruncher) that derives from QObject and defines num(self) and defines a signal 'done' emitted by num() before returning
call numberCruncher.moveToThread(thread)
connect the thread started signal to num
start the thread

How do I run multiple PyQt QTimers in a true concurrency manner?

I want to have two PyQt QTimers in my application where one performs the regular tasks that do not require much computation but, the other one performs some heavy workload. The two timers should run independently without anyone causing a delay in other's execution.
Currently, I have initialized two periodic timers such that, each times out after 10 milliseconds and calls its respective slot function. The code is shown below:
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QCoreApplication
import sys
import time
def first_timer_function():
# Some heavy task to be performed in this function
time.sleep(10)
print "First Timer Fired"
def second_timer_function():
# Regular tasks to be performed in this function
print "Second Timer Fired"
if __name__ == "__main__":
app = QCoreApplication.instance()
if app is None:
app = QApplication(sys.argv)
first_timer = QTimer()
first_timer.timeout.connect(first_timer_function)
first_timer.start(10)
second_timer = QTimer()
second_timer.timeout.connect(second_timer_function)
second_timer.start(10)
sys.exit(app.exec_())
The "first_timer" Timer has to perform the heavy computation simulated by the 10-second sleep. When I run this code, the "second_timer" suffers from a delay equal to the time taken by the "first_timer" slot function to perform its task. I read the documentation of QTimers and it turns out that all the QTimers run in one thread, therefore, the execution time of "first_timer" delays the "second_timer".
Should I just create two separate threads each running one of these Timers. What is ideally the best practice for achieving the true parallelism of QTimers?
First you need to convert your QThread into a python thread.
import threading
from threading import Timer, Thread, Event
class VideoCam(QThread):
def __init__(self):
super(VideoCam, self).__init__()
def run(self):
# do something
# add below
def run(self):
#"Launches the audio recording function using a thread"
self.thread = threading.Thread(target=self.run)
self.thread.start()
self.thread.join(1)
And there is a similar question about multiple timed event and there is also a good answer for it.
Can I run multiple Timers in Python simultaneously?
If I use this idea into my PyQt app, it will replace PyQt timer.
def start_painting(self):
#self.app_timer = QtCore.QTimer()
#self.app_timer.setInterval(control_values.timer_value)
#self.app_timer.timeout.connect(self.draw_2D_plot)
#self.app_timer.start()
timer_base = TimedEvent(1, self.draw_2D_plot)
timer_base.start()
timer_3d_painter = TimedEvent(0.3, self.draw_3D_plot)
timer_3d_painter.start()
Then adjust those (1 or 0.3) according to your app.
Hope this help.

Real-Time-Plotting using pyqtgraph and threading

this is a bit longer, the first part is just a description of the problem, the second one the question if my "fix" is correct.
I started with python programming. I created a program that communicates with an Arduino that reads the temperature of a furnace of our melting lab. The temperature is then used in a PID algorithm and an output is set to the Arduino. The communication is done via pyserial. So far, everthing works, including live plotting of the temperature signals, PID-variables and so on. The script includes a the main loop and 3 threads (serial communication, a datashifter that reads from serialport, the set temperature from the QWidget and the output of the PID algorithm. This values are used to create an array for displaying within pyqtgraph. Finally, the third thread shifts the data from the datashifter to the QWidget.
When using my Linux-Notebook, everything works fine, and the GUI never stops updating. In contrast, when using any Windows-Host, i have the problem that some pyqtgraphs stop to refresh. The behavior is strange, because i set all data at more or less the same time, with the same numpy array (just different columns) - some plots refresh longer (hours), some stop earlier (minutes). After searching more or less the hole internet ;-) I think that I found the problem: Its the passing of data from from a thread to the GUI. Some dummy code to explain what's going on:
DataUpdaterToGUI(QThread):
#sets the QWidget from main loop
def setGUI(self, gui):
self.gui = gui
def run()
while True:
with lock(): # RLock() Instance
copyArray = self.dataArray[:] # copy the array from the shifter
self.gui.plot1.copyArray(dataArray[:, 0], copyArray[:, 1])
self.gui.plot2.copyArray(dataArray[:, 0], copyArray[:, 2])
# self.gui.update()
# QApplication.instance().processEvents()
Neither calling self.gui.update() nor processEvents() has any influence on the outcome: The plots stop redrawing after a while (on windows).
Now i have a very simple example, and just want to make sure if I'm using the threading-stuff correctly. It works fine, but I have some questions:
Does the signal-slot approach copy the passed data?
Why is it not necessary to call the update() method of the QWidget?
Do I have to use any kind of locks when using signals?
class Main(QWidget):
def __init__(self):
super().__init__()
self.layout = QGridLayout(self)
self.graph = pg.PlotWidget()
self.graph.setYRange(0,1000)
self.plot = self.graph.plot()
self.layout.addWidget(self.graph,0,0)
self.show()
def make_connection(self, data_object):
data_object.signal.connect(self.grab_data)
#pyqtSlot(object)
def grab_data(self, data):
print(data)
self.plot.setData(data)
class Worker(QThread):
signal = pyqtSignal(object)
def __init__(self):
super().__init__()
def run(self):
self.data = [0, 1]
i = 2
while True:
self.data[1] = i
self.signal.emit(self.data)
time.sleep(0.01)
i += 1
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = Main()
worker = Worker()
widget.make_connection(worker)
worker.start()
sys.exit(app.exec_())
Does the signal-slot approach copy the passed data? The signals are thread-safe and when transferring data they make a copy so the thread that precedes the data and the thread that consumes it (GUI Thread) will not have conflicts
Why is it not necessary to call the update() method of the QWidget? Actually pyqtgraph calls the update method, plot is a PlotDataItem, so if we check the source code of setData() method, it calls the updateItems() method, in that method the setData() method of the curve or scatter attribute is called (according to the type of graphics), in the case of curve its setData() method calls updateData(), and the updateData() method calls update, and in the case of the scatter its setData() method calls addpoint(), and addPoints() calls invalidate(), and this invalidate() method calls update().
Do I have to use any kind of locks when using signals? No, as the signals are thread-safe so Qt already has the guards set to avoid the collision.

Creating Toplevel widgets that are thread safe

I'm trying to learn how to use the thread module. I followed along with the instructions here: http://effbot.org/zone/tkinter-threads.htm
My hope is the test script will:
Print out the "count" every two seconds
Show a pop-up dialog window (also every 2 seconds)
The pop-ups should be allowed to accumulate (if I don't click "OK" for a while, there should be
multiple pop-ups)
However, when I run this script it will freeze the main window and after a while crash. I think I'm not implementing the thread module correctly.
Could someone please have a look and point out what I'm doing wrong?
Here is what I've tried so far:
from Tkinter import *
import thread
import Queue
import time
class TestApp:
def __init__(self, parent):
self.super_Parent = parent
self.main_container = Frame(parent)
self.main_container.pack()
self.top_frame = Frame(self.main_container)
self.top_frame.pack(side=TOP)
self.bottom_frame = Frame(self.main_container)
self.bottom_frame.pack(side=TOP)
self.text_box = Text(self.top_frame)
self.text_box.config(height=20, width=20)
self.text_box.pack()
self.queue = Queue.Queue()
self.update_me()
def show_popup(self):
self.my_popup = Toplevel(self.main_container)
self.my_popup.geometry('100x100')
self.popup_label = Label(self.my_popup, text="Hello!")
self.popup_label.pack(side=TOP)
self.pop_button = Button(self.my_popup, text="OK", command=self.my_popup.destroy)
self.pop_button.pack(side=TOP)
def write(self, line):
self.queue.put(line)
def update_me(self):
try:
while 1:
line = self.queue.get_nowait()
if line is None:
self.text_box.delete(1.0, END)
else:
self.text_box.insert(END, str(line))
self.text_box.see(END)
self.text_box.update_idletasks()
except Queue.Empty:
pass
self.text_box.after(100, self.update_me)
def pipeToWidget(input, widget):
widget.write(input)
def start_thread():
thread.start_new(start_test, (widget,))
def start_test(widget):
count = 0
while True:
pipeToWidget(str(count) + "\n", widget)
count += 1
time.sleep(2)
widget.show_popup()
root = Tk()
widget = TestApp(root)
start_button = Button(widget.bottom_frame, command=start_thread)
start_button.configure(text="Start Test")
start_button.pack(side=LEFT)
root.title("Testing Thread Module")
root.mainloop()
I can't reproduce your problem, but I can see why it would happen.
You're using the queue to pass messages from the background thread to the main thread for updating text_box, which is correct. But you're also calling widget.show_popup() from the background thread, which means it creates and displays a new Toplevel in the background thread. That's not correct.
All UI code must run in the same thread—not all UI code for each top-level window, all UI code period. On some platforms, you may get away with running each window in its own thread (or even free-threading everything), but that isn't supposed to work, and definitely will crash or do improper things on some platforms. (Also, that single UI thread has to be the initial thread on some platforms, but that isn't relevant here.)
So, to fix this, you need to do the same dance for creating the popups that you do for updating the textbox.
The obvious way to do that is to move the widget.show_popup() to the loop in update_me(). If you want it to happen 2 seconds after the textbox updates, just add self.top_frame.after(2000, self.show_popup) to the method.
But I'm guessing you're trying to teach yourself how to have multiple independent updating mechanisms, so telling you "just use a single update queue for everything" may not be a good answer. In that case, just create two queues, and a separate update method servicing each queue. Then, do your pipeToWidget, sleep 2 seconds, then pipeToPopup.
Another way around this is to use mtTkinter. It basically does exactly what you're doing, but makes it automatic, pushing each actual Tk GUI call onto a queue to be run later by the main loop. Of course your objects themselves have to be thread-safe, and this also means that you have to deal with the GUI calls from one thread getting interleaved with calls from another thread. But as long as neither of those is a problem (and they don't seem to be in your case), it's like magic.
If you want to know why this is freezing and/or crashing for you on Win7 and not for me on OS X 10.8… well, you really need to look into a mess of Tcl, C, and Python code, and also at how each thing is built. And, unless it's something simple (like your Tk build isn't free-threaded), it wouldn't tell you much anyway. The code isn't supposed to work, and if it seems to work for me… that probably just means it would work every time until the most important demo of my career, at which point it would fail.

Categories

Resources