Как периодически проверять, закончен ли поток

#python #multithreading

Вопрос:

Я пытаюсь запустить сценарий, который выполняется асинхронно с использованием потоков. Я столкнулся с проблемой о том, как периодически проверять, жив ли поток (в разделе start_thread1). Я не хочу использовать join (), так как это заморозит графический интерфейс до тех пор, пока потоки не будут завершены.

Если это невозможно, я открыт для любых других способов сделать это.

Вот код, который я использую — это часть части кода, просто чтобы обрисовать «проблему», которая у меня есть:

 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()  

Комментарии:

1. Можете ли вы установить дочернему потоку логическое значение True непосредственно перед его выходом? Затем основной поток может просто проверить, что логическое значение, если оно все еще является значением по умолчанию/False, дочерний поток (вероятно) все еще работает; если это правда, вы можете безопасно вызвать join (), не беспокоясь о замораживании графического интерфейса.

2. @JeremyFriesner Не могли бы вы предоставить минимальный пример кода? Я не могу себе представить, как это сделать.

3. Почему бы вам просто не вызвать thread.is_alive (), чтобы проверить, запущен ли поток?

4. @PaulCornelius Я обновил вопрос тем, что я пробовал. Если я это сделаю, «если» запускается сразу после thread1.start (), и будет отображаться только пустой участок. Оператор if не позволит потоку завершить работу.

5. @Tibi добавьте self.running = False в нижнюю часть вашего worker_thread1(self) метода. Затем всякий раз, когда вы хотите проверить, join() вернется ли он быстро, вы можете if (client.running == False) проверить, завершен ли уже поток, и звонить только join() в том случае, если он уже завершен.

Ответ №1:

Если я правильно понимаю, у вас есть программа tkinter со вторым потоком, которая выполняет некоторый сбор данных, и вам нужно получить данные из этого второго потока обратно в графический интерфейс. Вы не можете просто ждать завершения второго потока, потому что это заблокировало бы основной цикл tkinter.

Одним из решений является создание функции обратного вызова, передача ее во второй поток и вызов ее в качестве самого последнего шага во втором потоке. Большинство объектов tkinter не являются потокобезопасными, поэтому, если вы собираетесь обновить графический интерфейс в функции обратного вызова, вам придется выполнить обратный вызов в основном потоке. Чтобы сделать это, основывайте обратный вызов на after_idle функции tkinter. Это приводит к тому, что обратный вызов происходит в цикле событий tk, в основном потоке, подобно обработчику событий tkinter.

Эта программа делает это и похожа на вашу программу. Я изменил несколько незначительных вещей, чтобы сделать мою проверку статического типа (pylint) счастливой. Я не использую matplotlib, поэтому я удалил этот код.

Самое важное уже внутри start_thread1 . Функция f объявляется и передается в качестве аргумента потоку. Обратите внимание, что f не вызывает processIncoming, а передает его в after_idle; это указывает основному циклу tk выполнить фактический вызов. Функция, переданная в worker_thread1, вызывается как последний шаг в потоке.

Конечным результатом является то, что processIncoming() запускается в основной поток, когда рабочий поток завершается.

 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()