簡體   English   中英

使用 Tkinter 和 Concurrent.Futures / ThreadPoolExecutor Class

[英]Using Tkinter With Concurrent.Futures / ThreadPoolExecutor Class

我一直在嘗試將兩者結合起來,但 ThreadPool 不會連接到 GUI。 甚至有可能將兩者聯系在一起嗎?

我只是以此為例來學習如何使用 ThreadPooling。 我希望每個名字都打印在它自己的線程上。 最終我將使用 Treeview 從它們自己的獨立線程收集數據,但不確定是否可以與 ThreadPoolExecutor 一起使用。

這是我嘗試過但無濟於事的方法:

from tkinter import Tk, Button, Listbox
from concurrent.futures import ThreadPoolExecutor
class MainWindow(Tk):
    def __init__(self):
        super().__init__()
    
        self.lb1 = Listbox(self, width=26, cursor='hand2')
        self.lb1.pack(side='left', fill='y', padx=20, pady=20)
    
        self.b1 = Button(self, text='START', bg='green', fg='white', cursor='hand2', command=self.start)
        self.b1.pack(side='left')
    
        self.lb1.insert('end', 'Aaron')
        self.lb1.insert('end', 'Billy')
        self.lb1.insert('end', 'Chris')
        self.lb1.insert('end', 'David')
        self.lb1.insert('end', 'Edward')
        self.lb1.insert('end', 'Frank')
        self.lb1.insert('end', 'George')
        self.lb1.insert('end', 'Howard')
        self.lb1.insert('end', 'Ian')
        self.lb1.insert('end', 'Johnny')
    
    def worker1(self):
        for i in range(self.lb1.size()):
            print(self.lb1.get(i))
    
    def start(self):
        with ThreadPoolExecutor(max_workers=2) as executor:
            executor.submit(self.worker1)



if __name__ == '__main__':
    app = MainWindow()
    app.title('Main Window')
    app.configure(bg='#333333')
    #center the Main Window:
    w = 500  # Width
    h = 420  # Height
    screen_width = app.winfo_screenwidth()  # Width of the screen
    screen_height = app.winfo_screenheight()  # Height of the screen
    # Calculate Starting X and Y coordinates for Window
    x = (screen_width / 2) - (w / 2)
    y = (screen_height / 2) - (h / 2)
    app.geometry('%dx%d+%d+%d' % (w, h, x, y))
    #app.maxsize(1400, 620)
    app.mainloop()

有沒有辦法讓他們一起工作?

線程池是一個高級任務分配概念,它必須以一種“事件循環”的方式管理工作線程和主線程之間的任務分配和同步。

問題是 tkinter 在您的主線程上運行着自己的事件循環,阻止它運行您的“線程池”循環是錯誤的,因為 GUI 將變得無響應。

tkinter本身從線程訪問是不安全的,可能會崩潰,所以你運行上面代碼的方式是不使用線程直接運行到主線程,但是如果你想使用線程可以直接使用threading模塊.

def start(self):
    thread = threading.Thread(target=self.worker1, daemon=True)
    thread.start()

上面的代碼不是線程安全的,tkinter 解釋器可能會崩潰,跨線程執行函數的正確方法是在主線程中對 GUI 進行所有查詢,然后再將其傳遞給子線程。

def start(self):
    labels = [self.lb1.get(i) for i in range(self.lb1.size())]
    thread = threading.Thread(target=self.print_func,args=(labels,), daemon=True)
    thread.start()
        
def print_func(self, labels):
    for item in labels:
        print(item)

上面的代碼是線程安全的,因為所有的 GUI 查詢都是在主線程中完成的,子線程只對遠離 tkinter 的數據進行操作,並且是在 tkinter 中使用線程的推薦方式。

為了將線程發出的命令返回到 GUI 主線程,您應該使用自定義信號和隊列進行通信。

MainWindow.bind("<<my_custom_signal>>",some_callback_function)  # called in init
MainWindow.event_generate("<<my_custom_signal>>")  # called in child thread

雖然可以直接從子線程向 GUI 發出命令,但不建議這樣做,因為它不是線程安全的,應用程序可能會崩潰或出現意外行為。

如果你必須運行一個線程池,你可以從一個子線程而不是你的主線程運行一個線程池,但是它不應該直接與你的 GUI 交互,你將只讓你的主線程管理 tkinter 事件循環和子線程管理線程池循環,管理更多的子線程。

def start(self):
    labels = [self.lb1.get(i) for i in range(self.lb1.size())]
    thread = threading.Thread(target=self.print_func, args=(labels,), daemon=True)
    thread.start()

def print_func(self, labels):  # called in child thread
    with ThreadPoolExecutor(max_workers=2) as executor:  # create child of child
        tasks = []
        for label in labels:
            task = executor.submit(print, label)  # just print in the child threadpool
            tasks.append(task)
        for task in tasks:
            task.result()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM