Preventing multiple instances of a thread in GTK - python

Upon pressing a listbox row, a thread is started which:
Clears the treeview (its liststore)
Then, adds items to the treeview (or rather its liststore).
The row can be pressed multiple times, meaning it can spawn multiple threads, which may end up running simultaneously.
The issue is that if the row is pressed very quickly multiple times, the treeview ends up with duplicate entries. This is caused by the multiple threads running in parallel, all trying to add the same set of items the treeview.
The solution to this issue would be to only ever allow one instance of the thread running at any time. This could be done by either:
Preventing new thread instances from starting if the thread is currently running
Stop the already running instance of the thread, and only then starting a new one
The problem is that I do not know how to implement either of those solutions.
Code:
class Main(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
self.connect("destroy", Gtk.main_quit)
self.liststore = Gtk.ListStore(str)
self.treeview = Gtk.TreeView()
self.treeview.set_model(self.liststore)
cellrenderertext = Gtk.CellRendererText()
treeviewcolumn = Gtk.TreeViewColumn("Items", cellrenderertext, text=0)
self.treeview.append_column(treeviewcolumn)
self.button = Gtk.Button(label='Button')
box = Gtk.Box()
box.set_orientation(Gtk.Orientation.VERTICAL)
box.add(self.button)
box.add(self.treeview)
self.add(box)
self.button.connect('clicked', self.button_clicked)
def button_clicked(self, widget):
def threaded():
self.liststore.clear()
self.liststore.append(['first_item'])
time.sleep(0.1)
self.liststore.append(['second_item'])
time.sleep(0.1)
self.liststore.append(['third_item'])
threading.Thread(target=threaded, daemon=True).start()
if __name__ == '__main__':
window = Main()
window.show_all()
Gtk.main()
I added the time.sleep(0.1) to simulate the delay between each item being added.
Pressing the button multiple times will result in duplicate entries appearing in the treeview. The expected output is:
first_item
second_item
third_item
However, pressing the button really quickly instead results in:
first_item
third_item
second_item
third_item

You should use variable to keep thread and check this variable.
In __init__ set variable
self.thread = None
and when you press button then check this variable
#if (self.thread in None) or (self.thread.is_alive() is False):
if not self.thread or not self.thread.is_alive():
self.thread = threading.Thread(...)
self.thread.start()
Instead of checking is_alive() you could set self.thread = None at the end of thread.

Note that all of the comments or answers that tell you how do deal with threading in Python might be partially correct for the narrow question on multi-threading, but they're not really what you want here.
You should realize that almost all the GTK/GLib API (like Gtk.ListStore) is not thread-safe unless explicitly mentioned. If you're going to call those functions from different threads than the main one, you should be prepared to encounter random and hard-to-debug issues.
In this case, if you want to update your Gtk.ListStore from a different thread, you want to make sure you use a function like GLib.idle_add() to update the store in the main thread.
You might also want to read up on the PyGObject tutorial on threading and concurrency

Related

Trying to show a countdown on Close while waiting on threads in Qt6

For now everything in my application is quite simple because I'm just starting building it. In the end there will be potentially two threads which might run for a long time. Not because they've got a lot of computing to do, but because I have to coninuously listen on COM ports and record the data.
Now during normal operation this is not a problem because it does what it should. Now it can happen though that the user wants to close the application before the measurement is done and the threads are still running.
I got that working too, however I'm not sure if my approach is the right one on that.
I don't want to use terminate since I'm not sure what happends to COM ports that are not closed properly and other things.
Since it can take a while for the threads to close (even on my simple loop thread it takes a few seconds) I wanted to show some little countdown to tell the user yes the shutdown is happening, it just takes a few seconds.
I tried to show it in a QtextEdit in the main widget. But I think that does not work because I'm already in the closeEvent in my approach. I didn't try an additional QDialog popup yet, but I guess it won't work either due to the same resons.
Is there a way to make something like this work to prevent the user from thinking that the program has crashed?
So this is how far I've gotten:
My worker object:
class Thread_Simulator(QtCore.QObject):
sigFin = QtCore.Signal()
sigText = QtCore.Signal(str)
def __init__(self):
super().__init__()
self.quit = 0
def run(self):
i = 0
while i < 10:
i += 1
self.sigText.emit(str(i))
if self.quit == 1:
#self.sigText.emit(__class__+" break")
break
sleep(2)
self.sigFin.emit()
def end(self):
self.quit = 1
Where I start the thread:
self.thread_test = QtCore.QThread()
self.worker_test = Thread_Simulator()
self.worker_test.moveToThread(self.thread_test)
self.thread_test.started.connect(self.worker_test.run)
self.worker_test.sigFin.connect(self.thread_test.quit)
self.worker_test.sigFin.connect(self.worker_test.deleteLater)
self.thread_test.finished.connect(self.thread_test.deleteLater)
self.worker_test.sigText.connect(self.simulator_update)
self.thread_test.start()
And finally my close event of the main application which happens when the user presses the x button I guess.
def closeEvent(self, event):
self.worker_test.end()
print("EXITING -- Waiting for Threads to shut down")
self.thread_test.wait(10000)
return super().closeEvent(event)
Another problem with this is also that if the thread is not running anymore there will be an error on close because the thread is already deleted. I might get around this by setting another variable with the finished signal of the thread and checking for that.
Maybe there is a better way to do this though. Also enabling me to show this countdown thing I thought of.
EDIT:
ok I tried things and it might be stupid but it seems to work this way. I thought that I might as well keep the thread and the worker alive until the end of the program since they don't change anyway and might get restarted. So if I keep them alive it might use up more memory but it is also quicker to restart the worker since they are still alive.
So I removed the deleteLater signal connections and call them manually in the closeEvent instead:
self.thread_test = QtCore.QThread()
self.worker_test = Thread_Simulator()
self.worker_test.moveToThread(self.thread_test)
self.thread_test.started.connect(self.worker_test.run)
self.worker_test.sigFin.connect(self.thread_test.quit)
self.worker_test.sigFin.connect(self.show_finished)
#self.worker_test.sigFin.connect(self.worker_test.deleteLater)
#self.thread_test.finished.connect(self.thread_test.deleteLater)
self.worker_test.sigText.connect(self.simulator_update)
self.thread_test.start()
I'm still not able to show some sort of message to inform the user about the shutdown process (the .append does not show anymore). The shutdown seems to work a little faster though and the "finised" signal gets still correctly emited by the worker.
def closeEvent(self, event): #: QtGui.QCloseEvent) -> None:
self.log.append("EXITING -- Waiting for Threads to shut down")
print("EXITING -- Waiting for Threads to shut down")
self.worker_test.end()
self.thread_test.quit()
self.worker_test.deleteLater()
self.thread_test.deleteLater()
self.thread_test.wait(10000)
return super().closeEvent(event)
I still don't really know if that is the correct way to do it, but it works that way for me.
I'm initializing an additional variable (self.end = None) in the init of the main window.
My closeEvent looks like this now:
def closeEvent(self, event):
if self.thread_test.isRunning():
print("Thread still running -- closing")
self.worker_test.end()
self.end = True
self.show_shutdown_message()
event.ignore()
elif self.thread_test.isFinished():
print("Thread finished -- ENDE")
self.worker_test.deleteLater()
self.thread_test.deleteLater()
self.thread_test.wait(10000)
event.accept()
the Slot of the sigFin signal of the worker thread checks for that and if it was set to anything else but none it will go to the closeEvent again after the worker has finished.
#QtCore.Slot()
def show_finished(self):
print("Worker finished")
if self.end is not None:
self.close()
And with the call of self.show_shutdown_message() I can also show a little QDialog window which informs the user about what is happening.
If anyone knows a more "official" way to do this I'm still open for suggestions though :)

Python - Run next thread when the previous one is finished

I'm writing a tkinter app.
I want to use Thread to avoid the tkinter window freezing but actually I did not find solution.
A quick part of my code (simplify):
from threading import Thread
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
search_button = tk.Button(self, text='Print', command=self.Running)
search_button.grid(row=0, column=0)
def funct1(self):
print('One')
def funct2(self):
print('Two')
def CreateThread(self, item):
self.item = item
t = Thread(target=self.item)
t.start()
def Running(self):
self.CreateThread(self.funct1)
# How to wait for the end of self.CreateThread(self.funct1) ?
self.CreateThread(self.funct2)
if __name__ == '__main__':
myGUI = App()
myGUI.mainloop()
How to wait for the self.CreateThread(self.funct1) ending before running self.CreateThread(self.funct2).
With a queue ?
With something else ?
I already have take a look to Thread.join() but it freez the tkinter window.
Hope you can help me :)
IMO you should think differently about what "Thread" means. A thread is not a thing that you run. A thread is a thing that runs your code. You have two tasks (i.e., things that need to be done), and you want those tasks to be performed sequentially (i.e., one after the other).
The best way to do things sequentially is to do them in the same thread. Instead of creating two separate threads, why not create a single thread that first calls funct1() and then calls funct2()?
def thread_main(self):
funct1()
funct2()
def Running(self):
Threead(target=self.thread_main).start()
P.S.: This could be a mistake:
def CreateThread(self, item):
self.item = item
t = Thread(target=self.item)
t.start()
The problem is, both of the threads are going to assign and use the same self.item attribute, and the value that is written by the first thread may be over-written by the second thread before the first thread gets to used it. Why not simply do this?
def CreateThread(self, item):
Thread(target=item).start()
Or, since the function body reduces to a single line that obviously creates and starts a thread, why even bother to define CreateThread(...) at all?
You can synchronize threads using locks. Hard to give a concrete answer without knowing what needs to be done with these threads. But, locks will probably solve your problem. Here's an article on synchronizing threads.

wxPython: frame class loads several commands AFTER it is called

I have a function that runs a few intensive commands, so I made a Spinner class, which is just a simple window that appears with a wx.Gauge widget that pulses during loading.
The problem is that, when called in Run, the window doesn't appear until several seconds after it was initialized - self.TriangulatePoints() actually finishes before the window appears. Indeed, if I don't comment out load.End() (which closes the window), the Spinner instance will appear and immediately disappear.
I assume this has something to do with threading, and the program continues to run while Spinner initiates. Is this the case? And if so, can you pause progression of Run() until the Spinner window appears?
It should also be noted that running time.sleep(n) after calling Spinner(...) does not change when in the program sequence it appears on screen.
def Run(self, event):
gis.points_packed = False
gis.triangulated = False
load = Spinner(self, style=wx.DEFAULT_FRAME_STYLE & (~wx.CLOSE_BOX) & (~wx.MAXIMIZE_BOX) ^ (wx.RESIZE_BORDER) & (~wx.MINIMIZE_BOX))
load.Update('Circle packing points...')
gis.boundary(infile=gis.loaded_boundary)
load.Pulse()
self.GetPoints(None, show=False)
load.Update("Triangulating nodes...")
self.TriangulatePoints(None, show=True)
load.End()
########################################################
class Spinner(wx.Frame):
def __init__(self, *args, **kwds):
super(Spinner, self).__init__(*args, **kwds)
self.SetSize((300,80))
self.SetTitle('Loading')
process = "Loading..."
self.font = wx.Font(pointSize = 12, family = wx.DEFAULT,
style = wx.NORMAL, weight = wx.BOLD,
faceName = 'Arial')
self.process_txt = wx.StaticText(self, -1, process)
self.process_txt.SetFont(self.font)
self.progress = wx.Gauge(self, id=-1, range=100, pos=(10,30), size=(280,15), name="Loading")
self.Update(process)
self.Centre()
self.Show(True)
def End(self):
self.Close(True)
def Update(self,txt):
dc = wx.ScreenDC()
dc.SetFont(self.font)
tsize = dc.GetTextExtent(txt)
self.process_txt.SetPosition((300/2-tsize[0]/2,10))
self.process_txt.SetLabel(txt)
self.progress.Pulse()
def Pulse(self):
self.progress.Pulse()
There doesn't seem to be any threads in the code you show, so it's really not clear why do you think this has anything to do with threading. Quite the contrary, in fact: AFAICS this is due to not using threads. You should run your long running ("intensive") code in a worker thread, then things would work and display correctly in the UI.
You can't block the main, UI thread for any non-trivial amount of time and still expect the UI to update correctly.
By adding wx.Yield() immediately after load.Update('...'), I was able to fix the problem.
I found the solution through a post that Robin Dunn (#RobinDunn), one of the original authors of wxPython, wrote in a Google group:
As Micah mentioned the various yield functions are essentially a
nested event loop that reads and dispatches pending events from the
event queue. When the queue is empty then the yield function returns.
The reason [wx.Yield()] fixes the problem you are seeing is that your long
running tasks are preventing control from returning to the main event
loop and so the paint event for your custom widget will just sit in
the queue until the long running task completes and control is
allowed to return to the main loop. Adding the yield allows those
events to be processed sooner, but you may still have problems when
the long running task does finally run because any new events that
need to be processed during that time (for example, the user clicks a
Cancel button) will still have to wait until the LRT is finished.
Another issue to watch out for when using a yield function is that it
could lead to an unexpected recursion. For example you have a LRT
that periodically calls yield so events can be processed, but one of
the events that happens is one whose event handler starts the LRT
again.
So usually it is better to use some other way to prevent blocking of
events while running a the LRT, such as breaking it up into chunks
that are run from EVT_IDLE handlers, or using a thread.

Infinite loops in Python threads

So I have two python threads running from inside a class. I have checked using
threading.active_count()
and it says both threads are running. The first thread includes a tkinter window which works fine. The second thread I am using as an event manager for the first window, which also works okay by itself. However, when I run the second thread alongside the first thread, the first thread does not work, ie. the window does not appear. This is even if the first thread is executed first. When I remove the infinite loop from the second thread, the first thread works again, can anyone explain this to me? Here is the class:
class Quiz(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def show(self, question):
self.question = quiz[question][0]
self.correct = quiz[question][1]
self.incorrectA = quiz[question][2]
self.incorrectB = quiz[question][3]
self.ref = quiz[question][4]
questionDisplay.config(text=self.question)
correctButton = "answer" + str(self.ref[0])
eval(correctButton).config(text=self.correct, command=lambda : check(True))
incorrect1 = "answer" + str(self.ref[1])
eval(incorrect1).config(text=self.incorrectA, command= lambda : check(False))
incorrect2 = "answer" + str(self.ref[2])
eval(incorrect2).config(text=self.incorrectB, command= lambda : check(False))
return self.correct
def run(self):
print("thread started")
print(threading.active_count())
while True:
print(questionQueue.qsize())
if questionQueue.qsize() >= 1:
pass
else:
pass
print("looped")
Thanks
From the code as currently shown it is not obvious where the problem lies. But keep the following in mind;
Tk is event-driven like basically all GUI toolkits. So for the GUI to work you need to run Tk's mainloop. The only pieces of your code that it runs in the main loop are the various callbacks attached to things like buttons, menus and timers.
Like most GUI toolkits Tk isn't thread-safe because of the overhead that would require. To keep it working properly, you should only call Tk functions and methods from one thread.
Python's threads are operating system threads. This means they are subject to operating system scheduling. And the OS sometimes gives more time to threads that are busy. So if a thread that is spinning in a busy-loop is pre-empted (as is done regularly), chances are that it ends up being run again instead of the GUI thread.

Destroying a Toplevel in a thread locks up root

I have a program I've been writing that began as a helper function for me to find a certain report on a shared drive based on some information in that report. I decided to give it a GUI so I can distribute it to other employees, and have ran into several errors on my first attempt to implement tkinter and threading.
I'm aware of the old adage "I had one problem, then I used threads, now I have two problems." The thread did, at least, solve the first problem -- so now on to the second....
My watered down code is:
class GetReport(threading.Thread):
def __init__(self,root):
threading.Thread.__init__(self)
# this is just a hack to get the StringVar in the new thread, HELP!
self.date = root.getvar('date')
self.store = root.getvar('store')
self.report = root.getvar('report')
# this is just a hack to get the StringVar in the new thread, HELP!
self.top = Toplevel(root)
ttk.Label(self.top,text="Fooing the Bars into Bazes").pack()
self.top.withdraw()
def run(self):
self.top.deiconify()
# a function call that takes a long time
self.top.destroy() #this crashes the program
def main():
root = Tk()
date,store,report = StringVar(),StringVar(),StringVar()
#####
## labels and Entries go here that define and modify those StringVar
#####
def launchThread(rpt):
report.set(rpt)
# this is just a hack to get the StringVar in the new thread, HELP!
root.setvar('date',date.get())
root.setvar('store',store.get())
root.setvar('report',report.get())
# this is just a hack to get the StringVar in the new thread, HELP!
reportgetter = GetReport(root)
reportgetter.start()
ttk.Button(root,text="Lottery Summary",
command=lambda: launchThread('L')).grid(row=1,column=3)
root.mainloop()
My expected output is for root to open and populate with Labels, Entries, and Buttons (some of which are hidden in this example). Each button will pull data from the Entries and send them to the launchThread function, which will create a new thread to perform the foos and the bars needed to grab the paperwork I need.
That thread will launch a Toplevel basically just informing the user that it's working on it. When it's done, the Toplevel will close and the paperwork I requested will open (I'm using ShellExecute to open a .pdf) while the Thread exits (since it exits its run function)
What's ACTUALLY happening is that the thread will launch its Toplevel, the paperwork will open, then Python will become non-responsive and need to be "end processed".
As far as I know you cannot use Threading to alter any GUI elements. Such as destroying a Toplevel window.
Any Tkinter code needs to be done in the main loop of your program.
Tkinter cannot accept any commands from threads other than the main thread, so launching a TopLevel in a thread will fail by design since it cannot access the Tk in the other thread. To get around this, use the .is_alive method of threads.
def GetReport(threading.Thread):
def __init__(self,text):
self.text = text
super().__init__()
def run(self):
# do some stuff that takes a long time
# to the text you're given as input
def main():
root = Tk()
text = StringVar()
def callbackFunc(text):
top = Toplevel(root)
ttk.Label(top,text="I'm working here!").pack()
thread = GetReport(text)
thread.start()
while thread.is_alive():
root.update() # this keeps the GUI updating
top.destroy() # only when thread dies.
e_text = ttk.Entry(root,textvariable=text).pack()
ttk.Button(root,text="Frobnicate!",
command = lambda: callbackFunc(text.get())).pack()

Categories