Search code examples
pythonpython-3.xtkinterpython-multithreading

How do I start a thread outside of the button command?


I'm making a simple GUI with Tkinter to subscribe and receive messages from an mqtt broker. I'm writing it myself because of some message decompressing issues, shouldn't matter. The thing is that when I connect and subscribe to a topic the client needs to stay running so I run it in a thread to avoid freezing the GUI. But because the thread stays running I can't rerun the subscribe action.

In the following way it works fine if you try once (please note that it is originally in an class, but I'm trying to only post the relevant methods):

    def main_window(self):
        self.root.title("MQTT Subscriber Test")
        self.root.geometry("350x150")

        # Topic subscription
        title = tk.Label(self.root, text="Enter topic to subscribe:")
        title.pack(**self.padding, anchor=tk.W)

        topic_entry = tk.Entry(self.root, textvariable=self.topic)
        self.root.update()
        topic_entry.pack(**self.padding, **self.ipadding, anchor=tk.W, fill=tk.X)

        # subscribe button
        subscribe_button = tk.Button(self.root, text="Connect", 
                                     command=threading.Thread(target=self.subscribe_to_topic).start)
        subscribe_button.pack(**self.padding, anchor=tk.W)

    def subscribe_to_topic(self):
        # ToDo: better condition for unsubscribe action
        if self.subbed_topic != "":
            self.client.unsubscribe_from_topic(self.subbed_topic)
            self.client.disconnect_from_broker()
        self.subbed_topic = self.get_topic
        self.open_output_window()
        self.client.operate(self.subbed_topic)

main_window() sets up the interface and subscribe_to_topic() is called when the subscribe button is pressed.

This runs fine once, but I can't re-press the button I get the error:

RuntimeError: threads can only be started once

So I tried switching the startup of the thread to the method itself like this:

    def main_window(self):
        self.root.title("MQTT Subscriber Test")
        self.root.geometry("350x150")

        # Topic subscription
        title = tk.Label(self.root, text="Enter topic to subscribe:")
        title.pack(**self.padding, anchor=tk.W)

        topic_entry = tk.Entry(self.root, textvariable=self.topic)
        self.root.update()
        topic_entry.pack(**self.padding, **self.ipadding, anchor=tk.W, fill=tk.X)

        # subscribe button
        subscribe_button = tk.Button(self.root, text="Connect", 
                                     command=self.subscribe_to_topic))
        subscribe_button.pack(**self.padding, anchor=tk.W)

    def subscribe_to_topic(self):
        # ToDo: better condition for unsubscribe action
        if self.subbed_topic != "":
            self.client.unsubscribe_from_topic(self.subbed_topic)
            self.client.disconnect_from_broker()
        self.subbed_topic = self.get_topic
        self.open_output_window()
        threading.Thread(target=self.client.operate(self.subbed_topic).start()

But that freezes the GUI again. From this point I couldn't find any other threads that solved my problem.


Solution

  • One thing that I noticed is that you are calling the function instead of passing the function to the thread.

    In this case, the function is being called target=self.client.operate(self.subbed_topic) and not being passed to the thread

    Try refactoring the last line like this, and trying again?

    threading.Thread(target=self.client.operate, args=(self.subbed_topic,)).start()