Tkinter GUI не отвечает после запуска потоков - PullRequest
0 голосов
/ 28 марта 2020

Я написал небольшой пример программы, которая должна запускать несколько пакетов параллельных потоков после нажатия start_button. Эта процедура может быть запущена только один раз, и после завершения одного пакета потоков предполагается, что пора выходить.

Так, например, он может go, например:
- Пакет 1 (10 потоков) работает, во время нажатия stop_button. После выполнения пакета 1 программа должна остановиться без запуска пакета 2 и вернуться в исходное состояние (снова имея возможность запустить эту процедуру).


Но, GUI, похоже, не в состоянии зарегистрировать щелчок или что-либо вообще во время этой процедуры. Это просто кажется замороженным. Так что я должен каким-то образом отделить потоки, которые делают свое дело, от GUI, которые делают свое дело, но я не знаю, как именно.

import threading
import tkinter as tk
import time
import random


class Blocking():
    def __init__(self):
        self.master = tk.Tk()
        self.master.geometry("400x400")

        self.start_button = tk.Button(self.master, command=self.long_task, text='press me to start', state='normal')
        self.start_button.pack()

        self.stop_button = tk.Button(self.master, command=self.stop_func, text='press me to stop', state='normal')
        self.stop_button.pack()
        self.long_task_was_stopped = False

        self.master.mainloop()

    def one_thread(self, thread_index):
        time.sleep(random.randint(5, 10))

    def long_task(self): # will run many batches of parallel one_thread functions on press of start_button
        self.start_button["state"] = 'disabled'
        # first batch of threads
        threads = []
        for thread_number in range(0,10):
            thread = threading.Thread(target=self.one_thread, args=(thread_number,))
            threads.append(thread)
            thread.start()

        for thread in threads:
            thread.join()

        print("First batch over!")
        # batch over, check if it was stopped
        print("Stop variable value:", self.long_task_was_stopped)
        if self.long_task_was_stopped == True:
            # reset states, quit function
            self.long_task_was_stopped = False
            self.start_button["state"] = 'normal'
            print("Stopped, exiting!")
            return

        # second batch of threads
        threads = []
        for thread_number in range(0,10):
            thread = threading.Thread(target=self.one_thread, args=(thread_number,))
            threads.append(thread)
            thread.start()

        for thread in threads:
            thread.join()

        print("Second batch over!")
        self.long_task_was_stopped = False
        self.start_button["state"] = 'normal'
        print("Done.")
        return

    def stop_func(self):
        print("Trying to stop...")
        self.long_task_was_stopped = True



if __name__ == '__main__':
    block = Blocking()  



РЕДАКТИРОВАТЬ: Кажется, решение состоит в том, чтобы продолжать вызывать update() в главном окне Tkinter после запуска потоков и проверять, пока все потоки не закончатся, прежде чем продолжить, для этого необходимы какой-то счетчик и threading.Lock(). Вот решение.

import threading
import tkinter as tk
import time
import random


class Blocking():
    def __init__(self):
        self.master = tk.Tk()
        self.master.geometry("400x400")

        self.start_button = tk.Button(self.master, command=self.long_task, text='press me to start', state='normal')
        self.start_button.pack()

        self.stop_button = tk.Button(self.master, command=self.stop_func, text='press me to stop', state='normal')
        self.stop_button.pack()
        self.long_task_was_stopped = False

        self.LOCK = threading.Lock()
        self.count_of_done_threads = 0
        self.master.mainloop()


    def one_thread(self, thread_index):
        time.sleep(random.randint(5, 10))
        with self.LOCK:
            print("Thread", thread_index, "done.")
            self.count_of_done_threads = self.count_of_done_threads +1

    def long_task(self): # will run many batches of parallel one_thread functions on press of start_button
        self.start_button["state"] = 'disabled'
        self.long_task_was_stopped = False

        # first batch of threads
        threads = []
        for thread_number in range(0,10):
            thread = threading.Thread(target=self.one_thread, args=(thread_number,))
            threads.append(thread)
            thread.start()

        # wait until threads are done
        while 1:
            self.master.update()
            if self.count_of_done_threads == 10: # 10 here is size of batch
                break
        self.count_of_done_threads = 0

        print("First batch over!")
        # batch over, check if it was stopped
        print("Stop variable value:", self.long_task_was_stopped)
        if self.long_task_was_stopped == True:
            # reset states, quit function
            self.long_task_was_stopped = False
            self.start_button["state"] = 'normal'
            print("Stopped, exiting!")
            return

        # second batch of threads
        threads = []
        for thread_number in range(0,10):
            thread = threading.Thread(target=self.one_thread, args=(thread_number,))
            threads.append(thread)
            thread.start()

        # wait until threads are done
        while 1:
            self.master.update()
            if self.count_of_done_threads == 10:
                break
        self.count_of_done_threads = 0

        print("Second batch over!")
        self.long_task_was_stopped = False
        self.start_button["state"] = 'normal'
        print("Done.")
        return

    def stop_func(self):
        print("Trying to stop...")
        self.long_task_was_stopped = True

if __name__ == '__main__':
    block = Blocking()  

1 Ответ

0 голосов
/ 28 марта 2020

Вы должны использовать non-block thread. Просто thread.start() все в порядке. В официальном документе :

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

Это означает, что только когда ваша функция one_thread fini sh заработает, ваш код будет запущен.

Ваш код может быть следующим:

import threading
import tkinter as tk
import time
import random


class Blocking():
    def __init__(self):
        self.master = tk.Tk()
        self.master.geometry("400x400")

        self.start_button = tk.Button(self.master, command=self.long_task, text='press me to start', state='normal')
        self.start_button.pack()

        self.stop_button = tk.Button(self.master, command=self.stop_func, text='press me to stop', state='normal')
        self.stop_button.pack()
        self.long_task_was_stopped = False

        self.master.mainloop()

    def one_thread(self, thread_index):
        time.sleep(random.randint(5, 10))

    def long_task(self): # will run many batches of parallel one_thread functions on press of start_button
        self.start_button["state"] = 'disabled'
        # first batch of threads
        threads = []
        for thread_number in range(0,10):
            thread = threading.Thread(target=self.one_thread, args=(thread_number,))
            threads.append(thread)
            thread.start()

        print("First batch over!")
        # batch over, check if it was stopped
        print("Stop variable value:", self.long_task_was_stopped)
        if self.long_task_was_stopped == True:
            # reset states, quit function
            self.long_task_was_stopped = False
            self.start_button["state"] = 'normal'
            print("Stopped, exiting!")
            return

        # second batch of threads
        threads = []
        for thread_number in range(0,10):
            thread = threading.Thread(target=self.one_thread, args=(thread_number,))
            threads.append(thread)
            thread.start()

        print("Second batch over!")
        self.long_task_was_stopped = False
        self.start_button["state"] = 'normal'
        print("Done.")
        return

    def stop_func(self):
        print("Trying to stop...")
        self.long_task_was_stopped = True

if __name__ == '__main__':
    block = Blocking()  

Мой минимальный пример удаления веб-изображения. (Когда вы запустите этот код, он покажет loading... при окончании sh очистки, это покажет картинку).

import tkinter
from PIL import ImageTk,Image
import threading,requests
from io import BytesIO

def scrapImage():
    image = ImageTk.PhotoImage(Image.open(BytesIO(requests.get('https://s2.ax1x.com/2020/02/08/1Wl4mT.md.jpg').content)).resize((200,200))) # crawl my image
    imagePos.image = image # keep reference.
    imagePos['image'] = image


root = tkinter.Tk()
thread = threading.Thread(target=scrapImage)
thread.start()

imagePos = tkinter.Label(text="loading.....")
imagePos.grid()

root.mainloop()
...