Home > Mobile >  How to check periodically if a thread is finished
How to check periodically if a thread is finished

Time:11-11

I am trying to run a script which runs Asynchronously using threadings. I have run into an issue on how to check periodically if a thread is still alive (under start_thread1). I don't want to use join() as that will freeze the GUI until the threads is finished.

If that is not possible, I am open-minded with any other ways of doing it.

Here is the code I am using - this is a part of part of the code just to outline the "issue" that I have:

from tkinter.constants import LEFT, RIGHT, S
import tkinter.messagebox
from matplotlib import pyplot as plt


import tkinter as tk, time, threading, random, queue

class GuiPart(object):
    def __init__(self, master, queue, queue2, client_instance):
        self.queue = queue
        self.queue2 = queue2
        self.x = []
        self.y= []
        # Set up the GUI

        self.Button2 =  tk.Button(master, text="Button2", padx=10, 
                            pady=5, fg="white", bg="#263D42", command=client_instance.start_thread1)   

        self.Button2.pack(side = RIGHT)


    def processIncoming(self): 

        """ Handle all messages currently in the queue, if any. """
        while not self.queue.empty():
            msg = self.queue.get_nowait()
            self.x.append(msg)
            print(msg)
 
        while not self.queue2.empty():
            msg2 = self.queue2.get_nowait()
            self.y.append(msg2)

        fig, ax = plt.subplots()
        ax.plot(self.x, self.y)

        plt.show()


class ThreadedClient(object):
    """
    Launch the main part of the GUI and the worker thread. periodic_call()
    and end_application() could reside in the GUI part, but putting them
    here means that you have all the thread controls in a single place.
    """
    def __init__(self, master):
        """
        Start the GUI and the asynchronous threads.  We are in the main
        (original) thread of the application, which will later be used by
        the GUI as well.  We spawn a new thread for the worker (I/O).
        """
        self.master = master
        # Create the queue
        self.queue = queue.Queue()
        self.queue2 = queue.Queue()

        self.running=True

        # Set up the GUI part
        self.gui = GuiPart(master, self.queue, self.queue2, self)

        # Set up the thread to do asynchronous I/O
        # More threads can also be created and used, if necessary
    
    def start_thread1(self):
    
        thread1=threading.Thread(target=self.worker_thread1)
        thread1.start()

       # how to check periodically if the thread is finished and when is finished run self.gui.processIncoming()
       # if I run it straight away like this the self.gui.processIncoming() will run before the thread will finish and nothing will be plotted

        if thread1.is_alive() == False:
            self.gui.processIncoming()
    
    def worker_thread1(self):
        """
        This is where we handle the asynchronous I/O.  For example, it may be
        a 'select()'.  One important thing to remember is that the thread has
        to yield control pretty regularly, be it by select or otherwise.
        """
        if self.running:
            time.sleep(5)   # I am using time.sleep(5) just to simulate a long-running process
            for i in range(20):
                msg = i
                self.queue.put(msg)
            for j in range(20):
                msg2 = j
                self.queue2.put(msg2)


root = tk.Tk()
root.title('Matplotlib threading')
client = ThreadedClient(root)
root.mainloop()

CodePudding user response:

If I understand correctly, you have a tkinter program with a second thread that does some data collection, and you need to get data from that second thread back into the gui. You can't simply wait for the second thread to finish because that would block the tkinter main loop.

One solution is to create a callback function, pass it to the second thread, and call it as the very last step in the second thread. Most tkinter objects aren't threadsafe, so if you're going to update the GUI in the callback function, you have to run the callback in the main thread. To do this, base the callback on tkinter's after_idle function. This causes the callback to occur in tk's event loop, in the main thread, much like a tkinter event handler.

This program does that, and is similar to your program. I changed a few minor things to make my static type checker (pylint) happy. I don't use matplotlib so I took that code out.

The important stuff is in start_thread1. The function f is declared and passed as an argument to the thread. Note that f doesn't call processIncoming, but passes it to after_idle; that instructs the tk main loop to perform the actual call. The function that got passed to worker_thread1 is called as the last step in the thread.

The end result is that processIncoming() is fired into the main thread when the worker thread finishes.

from tkinter.constants import RIGHT

import tkinter as tk
import time
import threading
import queue

class GuiPart:
    def __init__(self, master, queue1, queue2, client_instance):
        self.queue1 = queue1
        self.queue2 = queue2
        self.x = []
        self.y= []
        # Set up the GUI

        self.Button2 = tk.Button(master, text="Button2", padx=10,
                                 pady=5, fg="white", bg="#263D42",
                                 command=client_instance.start_thread1)

        self.Button2.pack(side = RIGHT)


    def processIncoming(self): 
        """ Handle all messages currently in the queue, if any. """
        print(threading.current_thread())
        while not self.queue1.empty():
            msg = self.queue1.get_nowait()
            self.x.append(msg)
            print("X", msg)
 
        while not self.queue2.empty():
            msg2 = self.queue2.get_nowait()
            self.y.append(msg2)
            print("Y", msg2)

        print("Make a plot now")

class ThreadedClient:
    """
    Launch the main part of the GUI and the worker thread. periodic_call()
    and end_application() could reside in the GUI part, but putting them
    here means that you have all the thread controls in a single place.
    """
    def __init__(self, master):
        """
        Start the GUI and the asynchronous threads.  We are in the main
        (original) thread of the application, which will later be used by
        the GUI as well.  We spawn a new thread for the worker (I/O).
        """
        self.master = master
        # Create the queue
        self.queue1 = queue.Queue()
        self.queue2 = queue.Queue()

        self.running=True

        # Set up the GUI part
        self.gui = GuiPart(master, self.queue1, self.queue2, self)

        # Set up the thread to do asynchronous I/O
        # More threads can also be created and used, if necessary
    
    def start_thread1(self):
        def f():
            self.master.after_idle(self.gui.processIncoming)
        thread1=threading.Thread(target=self.worker_thread1, args=(f, ))
        thread1.start()
    
    def worker_thread1(self, callback):
        """
        This is where we handle the asynchronous I/O.  For example, it may be
        a 'select()'.  One important thing to remember is that the thread has
        to yield control pretty regularly, be it by select or otherwise.
        """
        print(threading.current_thread())
        if self.running:
            time.sleep(1)   # simulate a long-running process
            for i in range(20):
                msg = i
                self.queue1.put(msg)
            for j in range(20):
                msg2 = j
                self.queue2.put(msg2)
        callback()


root = tk.Tk()
root.title('Matplotlib threading')
client = ThreadedClient(root)
root.mainloop()
  • Related