Preventing multiple instances of a thread in GTK

Question:

Upon pressing a listbox row, a thread is started which:

  1. Clears the treeview (its liststore)
  2. 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

Asked By: user_968563

||

Answers:

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.

Answered By: furas

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 none of 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

Answered By: nielsdg