Despite using a QThread, the GUI is still freezing
(code posted at end)
The space bar is hit which starts playing midi notes by creating the thread and emitting calls to the play function
if self.playing is False:
# PlayThread is initiated in PianoRoll when the space bar is hit.
Loop iterates and plays data, freezing the GUI
else:
# This section is not reached because another space bar hit cannot be received while data is looping
I have viewed the following tutorials and a variety of responses on StackOverflow:
https://manojbits.wordpress.com/2013/01/24/threading-in-pyqt4/
the joplaete tutorial
I've tried the following:
placing all looping code to run in the signaled function
placing the loop in the thread's run function and passing the data to the signaled function with emit
separating the function from the widget that handling the calls to it
making the data structures global (trying anything)
Please let me know what I am missing or if any other code is needed.
I am using an open source MIDI sequencer template
https://github.com/rhetr/seq-gui
class PlayThread(QtCore.QThread):
def __init__(self):
# qtcore.QThread.__init__(self, parent=app)
super(PlayThread , self).__init__()
self.signal = QtCore.SIGNAL("signal")
def run(self):
global xLocToNoteItems
# this loop was attempted in the signaled function as well
for xloc in xLocToNoteItems:
self.emit(self.signal, xloc)
# self.emit(self.signal, "arbitrary?")
# self.emit(QtCore.SIGNAL('update(QString)') + str(i))
in piano roll:
if event.key() == QtCore.Qt.Key_Space:
if self.playing:
self.playing = False
# terminate play thread
main.playThread.quit()
else:
self.playing = True
# playThread previously attempted to be stored in this (piano roll) class
# ...was moved to main in case the call was freezing the piano roll
main.playThread = PlayThread()
main.connect(main.playThread, main.playThread.signal, main.play)
main.playThread.start()
the play function was in the piano roll widget, now in main
def play(self, xloc):
# for xloc in main.xLocToNoteItems:
global xLocToRhythms
global xLocToNoteItems
for noteItem in xLocToNoteItems[xloc]:
player.note_on(noteItem.note[0], noteItem.note[3], 1)
offtime = xLocToRhythms[xloc]
time.sleep(offtime)
for noteItem in xLocToNoteItems[xloc]:
player.note_off(noteItem.note[0], noteItem.note[3], 1)
Your thread is deliberately emitting more than one signal to execute the play() method in the main thread. Your play method must be running for a reasonable amount of time and blocking the main thread (it for instance has a time.sleep in it).
You probably need to move the play code into the thread as well, but only if the MIDI library you are using is safe to call from a secondary thread. Note that you should also check if the library is thread safe if you plan to call the library from multiple threads.
Related
I have a script that uses user input and based on that it generates some data (code below). The script uses threads so the GUI does not freeze while the background tasks are running.
My question is if it is possible to pause the worker thread, ask the user for some input, send it to the worker thread and then to continue the operation?
from tkinter.constants import LEFT, RIGHT, S
import tkinter.messagebox
import tkinter as tk, time, threading, random, queue
from tkinter import NORMAL, DISABLED, filedialog
class GuiPart(object):
def __init__(self, master, client_instance):
canvas = tk.Canvas(master, height=600, width=1020, bg="#263D42")
canvas.pack()
self.button1 = tk.Button(master, text="Mueller Matrix - MMP mode", padx=10,
pady=5, bd = 10, font=12, command=client_instance.start_thread1)
self.button1.pack(side = LEFT)
def files(self):
self.filena = filedialog.askopenfilenames(initialdir="/", title="Select File",
filetypes = (("comma separated file","*.csv"), ("all files", "*.*")))
return self.filena
def processing_done(self):
tkinter.messagebox.showinfo('Processing Done', 'Process Successfully Completed!')
return
class ThreadedClient(object):
def __init__(self, master):
self.running=True
self.gui = GuiPart(master, self)
def start_thread1(self):
thread1=threading.Thread(target=self.worker_thread1)
thread1.start()
def worker_thread1(self):
if self.running:
self.gui.button1.config(state=DISABLED) # can I disable the button from the main thread?
user_input = self.gui.files() # can I pause the Worker thread here so ask the user_input from main loop?
time.sleep(5) # to simulate a long-running process
self.gui.processing_done()
self.gui.button1.config(state=NORMAL)
root = tk.Tk()
root.title('test_GUI')
client = ThreadedClient(root)
root.mainloop()
I am asking this as from time to time my script ends abruptly saying that the GUI is not part of the main loop. Thank you for your help!
In general, you should run the GUI from the main thread.
There is some confusion whether tkinter is thread-safe. That is, if one can call tkinter functions and methods from any but the main thread. The documentation for Python 3 says “yes”. Comments in the C source code for tkinter say “its complicated” depending on how tcl is built.
You should be able to call tkinter functions and methods from additional threads in Python 3 if your tkinter was built with thread support. AFAIK, the tkinter that comes with the downloads from python.org have threading enabled.
If you want to be able to pause additional threads, you should create a global variable, e.g. pause = False.
The extra threads should regularly read the value of this and pause their calculations when set to true, e.g.:
def thread():
while run:
while pause:
time.sleep(0.2)
# do stuff
Meanwhile the GUI running in the main thread should be the only one to write the value of pause, e.g. in a widget callback.
Some sample callbacks:
def start_cb():
pause = False # So it doesn't pause straight away.
run = True
def stop_cb():
pause = False
run = False
def pause_cb():
pause = True
def continue_cb():
pause = False
If you have more complicated global data structures that can be modified be several threads, you should definitely guard access to those with a Lock.
Edit1
Threaded tkinter programs without classes:
Simple threaded tkinter example program
Real-world example for unlocking ms-excel files
Edit2:
From the python source code for _tkinter:
If Tcl is threaded, this approach won't work anymore. The Tcl
interpreter is only valid in the thread that created it, and all Tk
activity must happen in this thread, also. That means that the
mainloop must be invoked in the thread that created the
interpreter. Invoking commands from other threads is possible;
_tkinter will queue an event for the interpreter thread, which will
then execute the command and pass back the result. If the main
thread is not in the mainloop, and invoking commands causes an
exception; if the main loop is running but not processing events,
the command invocation will block.
You can read the whole comment here.
I'm writing a Python program to interact with a device based on a CAN Bus. I'm using the python-can module successfully for this purpose. I'm also using asyncio to react to asynchronous events. I have written a "CanBusManager" class that is used by the "CanBusSequencer" class. The "CanBusManager" class takes care of generating/sending/receiving messages, and the CanBusSequencer drives the sequence of messages to be sent.
At some point in the sequence I want to wait until a specific message is received to "unlock" the remaining messages to be sent in the sequence. Overview in code:
main.py
async def main():
event = asyncio.Event()
sequencer = CanBusSequencer(event)
task = asyncio.create_task(sequencer.doSequence())
await task
asyncio.run(main(), debug=True)
canBusSequencer.py
from canBusManager import CanBusManager
class CanBusSequencer:
def __init__(self, event)
self.event = event
self.canManager = CanBusManager(event)
async def doSequence(self):
for index, row in self.df_sequence.iterrows():
if:...
self.canManager.sendMsg(...)
else:
self.canManager.sendMsg(...)
await self.event.wait()
self.event.clear()
canBusManager.py
import can
class CanBusManager():
def __init__(self, event):
self.event = event
self.startListening()
**EDIT**
def startListening(self):
self.msgNotifier = can.Notifier(self.canBus, self.receivedMsgCallback)
**EDIT**
def receivedMsgCallback(self, msg):
if(msg == ...):
self.event.set()
For now my program stays by the await self.event.wait(), even though the relevant message is received and the self.event.set() is executed. Running the program with debug = True reveals an
RuntimeError: Non-thread-safe operation invoked on an event loop other than the current one
that I don't really get. It has to do with the asyncio event loop, somehow not properly defined/managed. I'm coming from the C++ world and I'm currently writing my first large program with Python. Any guidance would be really appreciated:)
Your question doesn't explain how you arrange for receivedMsgCallback to be invoked.
If it is invoked by a classic "async" API which uses threads behind the scenes, then it will be invoked from outside the thread that runs the event loop. According to the documentation, asyncio primitives are not thread-safe, so invoking event.set() from another thread doesn't properly synchronize with the running event loop, which is why your program doesn't wake up when it should.
If you want to do anything asyncio-related, such as invoke Event.set, from outside the event loop thread, you need to use call_soon_threadsafe or equivalent. For example:
def receivedMsgCallback(self, msg):
if msg == ...:
self.loop.call_soon_threadsafe(self.event.set)
The event loop object should be made available to the CanBusManager object, perhaps by passing it to its constructor and assigning it to self.loop.
On a side note, if you are creating a task only to await it immediately, you don't need a task in the first place. In other words, you can replace task = asyncio.create_task(sequencer.doSequence()); await task with the simpler await sequencer.doSequence().
I have a question on multiprocessing and tkinter. I am having some problems getting my process to function parallel with the tkinter GUI. I have created a simple example to practice and have been reading up to understand the basics of multiprocessing. However when applying them to tkinter, only one process runs at the time. (Using Multiprocessing module for updating Tkinter GUI) Additionally, when I added the queue to communicate between processes, (How to use multiprocessing queue in Python?), the process won't even start.
Goal:
I would like to have one process that counts down and puts the values in the queue and one to update tkinter after 1 second and show me the values.
All advice is kindly appreciated
Kind regards,
S
EDIT: I want the data to be available when the after method is being called. So the problem is not with the after function, but with the method being called by the after function. It will take 0.5 second to complete the calculation each time. Consequently the GUI is unresponsive for half a second, each second.
EDIT2: Corrections were made to the code based on the feedback but this code is not running yet.
class Countdown():
"""Countdown prior to changing the settings of the flows"""
def __init__(self,q):
self.master = Tk()
self.label = Label(self.master, text="", width=10)
self.label.pack()
self.counting(q)
# Countdown()
def counting(self, q):
try:
self.i = q.get()
except:
self.label.after(1000, self.counting, q)
if int(self.i) <= 0:
print("Go")
self.master.destroy()
else:
self.label.configure(text="%d" % self.i)
print(i)
self.label.after(1000, self.counting, q)
def printX(q):
for i in range(10):
print("test")
q.put(9-i)
time.sleep(1)
return
if __name__ == '__main__':
q = multiprocessing.Queue()
n = multiprocessing.Process(name='Process2', target=printX, args = (q,))
n.start()
GUI = Countdown(q)
GUI.master.mainloop()
Multiprocessing does not function inside of the interactive Ipython notebook.
Multiprocessing working in Python but not in iPython As an alternative you can use spyder.
No code will run after you call mainloop until the window has been destroyed. You need to start your other process before you call mainloop.
You are calling wrong the after function. The second argument must be the name of the function to call, not a call to the function.
If you call it like
self.label.after(1000, self.counting(q))
It will call counting(q) and wait for a return value to assign as a function to call.
To assign a function with arguments the syntax is
self.label.after(1000, self.counting, q)
Also, start your second process before you create the window and call counting.
n = multiprocessing.Process(name='Process2', target=printX, args = (q,))
n.start()
GUI = Countdown(q)
GUI.master.mainloop()
Also you only need to call mainloop once. Either position you have works, but you just need one
Edit: Also you need to put (9-i) in the queue to make it count down.
q.put(9-i)
Inside the printX function
That's the basic idea.
I couldn't understand them while reading about all this in python's documentation. There's simply too much of it to understand it.
Could someone explain me how to get a working progress bar with a multi-threaded(or multi-process) client?
Or is there any other way to "update" progress bar without locking program's GUI?
Also I did read something about "I/O" errors when these types of clients try to access a file at the same time & X-server errors when an application doesn't call multi-thread libs correctly. How do I avoid 'em?
This time I didn't write code, I don't want to end with a zombie-process or something like that (And then force PC to shutdown, I would hate to corrupt precious data...). I need to understand what am I doing first!
Something like the below might get you started? - Ive tried to annotate as much as possible to explain the process.
from Tkinter import *
from Queue import Queue
import ttk, threading
import time
queue = Queue()
root = Tk()
# Function to do 'stuff' and place object in queue for later #
def foo():
# sleep to demonstrate thread doing work #
time.sleep(5)
obj = [x for x in range(0,10)]
queue.put(obj)
# Create thread object, targeting function to do 'stuff' #
thread1 = threading.Thread(target=foo, args=())
# Function to check state of thread1 and to update progressbar #
def progress(thread, queue):
# starts thread #
thread.start()
# defines indeterminate progress bar (used while thread is alive) #
pb1 = ttk.Progressbar(root, orient='horizontal', mode='indeterminate')
# defines determinate progress bar (used when thread is dead) #
pb2 = ttk.Progressbar(root, orient='horizontal', mode='determinate')
pb2['value'] = 100
# places and starts progress bar #
pb1.pack()
pb1.start()
# checks whether thread is alive #
while thread.is_alive():
root.update()
pass
# once thread is no longer active, remove pb1 and place the '100%' progress bar #
pb1.destroy()
pb2.pack()
# retrieves object from queue #
work = queue.get()
return work
work = progress(thread1, queue)
root.mainloop()
Hope this helps. Let me know what you think!
I have a loading widget that consists of two labels, one is the status label and the other one is the label that the animated gif will be shown in. If I call show() method before heavy stuff gets processed, the gif at the loading widget doesn't update itself at all. There's nothing wrong with the gif btw(looping problems etc.). The main code(caller) looks like this:
self.loadingwidget = LoadingWidgetForm()
self.setCentralWidget(self.loadingwidget)
self.loadingwidget.show()
...
...
heavy stuff
...
...
self.loadingwidget.hide()
The widget class:
class LoadingWidgetForm(QWidget, LoadingWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setupUi(self)
self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
pince_directory = SysUtils.get_current_script_directory() # returns current working directory
self.movie = QMovie(pince_directory + "/media/loading_widget_gondola.gif", QByteArray())
self.label_Animated.setMovie(self.movie)
self.movie.setScaledSize(QSize(50, 50))
self.movie.setCacheMode(QMovie.CacheAll)
self.movie.setSpeed(100)
self.movie.start()
self.not_finished=True
self.update_thread = Thread(target=self.update_widget)
self.update_thread.daemon = True
def showEvent(self, QShowEvent):
QApplication.processEvents()
self.update_thread.start()
def hideEvent(self, QHideEvent):
self.not_finished = False
def update_widget(self):
while self.not_finished:
QApplication.processEvents()
As you see I tried to create a seperate thread to avoid workload but it didn't make any difference. Then I tried my luck with the QThread class by overriding the run() method but it also didn't work. But executing QApplication.processEvents() method inside of the heavy stuff works well. I also think I shouldn't be using seperate threads, I feel like there should be a more elegant way to do this. The widget looks like this btw:
Processing...
Full version of the gif:
Thanks in advance! Have a good day.
Edit: I can't move the heavy stuff to a different thread due to bugs in pexpect. Pexpect's spawn() method requires spawned object and any operations related with the spawned object to be in the same thread. I don't want to change the working flow of the whole program
In order to update GUI animations, the main Qt loop (located in the main GUI thread) has to be running and processing events. The Qt event loop can only process a single event at a time, however because handling these events typically takes a very short time control is returned rapidly to the loop. This allows the GUI updates (repaints, including animation etc.) to appear smooth.
A common example is having a button to initiate loading of a file. The button press creates an event which is handled, and passed off to your code (either via events directly, or via signals). Now the main thread is in your long-running code, and the event loop is stalled — and will stay stalled until the long-running job (e.g. file load) is complete.
You're correct that you can solve this with threads, but you've gone about it backwards. You want to put your long-running code in a thread (not your call to processEvents). In fact, calling (or interacting with) the GUI from another thread is a recipe for a crash.
The simplest way to work with threads is to use QRunner and QThreadPool. This allows for multiple execution threads. The following wall of code gives you a custom Worker class that makes it simple to handle this. I normally put this in a file threads.py to keep it out of the way:
import sys
from PyQt5.QtCore import QObject, QRunnable
class WorkerSignals(QObject):
'''
Defines the signals available from a running worker thread.
error
`tuple` (exctype, value, traceback.format_exc() )
result
`dict` data returned from processing
'''
finished = pyqtSignal()
error = pyqtSignal(tuple)
result = pyqtSignal(dict)
class Worker(QRunnable):
'''
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()
#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
To use the above, you need a QThreadPool to handle the threads. You only need to create this once, for example during application initialisation.
threadpool = QThreadPool()
Now, create a worker by passing in the Python function to execute:
from .threads import Worker # our custom worker Class
worker = Worker(fn=<Python function>) # create a Worker object
Now attach signals to get back the result, or be notified of an error:
worker.signals.error.connect(<Python function error handler>)
worker.signals.result.connect(<Python function result handler>)
Then, to execute this Worker, you can just pass it to the QThreadPool.
threadpool.start(worker)
Everything will take care of itself, with the result of the work returned to the connected signal... and the main GUI loop will be free to do it's thing!