如何在线程中运行长函数以防止GUI冻结?

时间:2015-09-27 12:58:46

标签: python multithreading user-interface tkinter

我对编程很新,正在制作一个程序,用于搜索文件中的橄榄球运动员信息(约123,300名玩家),并在Python 3中显示符合用户搜索条件的玩家tkinter < / p>

我有一个绑定到search()方法的搜索按钮,该方法获取用户输入,调用run_search搜索文件以查找匹配项,并将匹配项显示给画布。它工作正常,除了按下按钮时GUI冻结,并且只有在几秒钟后搜索完成并且匹配的玩家信息被绘制到画布时解冻。 search()方法的实际搜索功能是用不同的.py文件,player_search_engine编写的。

到目前为止,我了解tkinter以串行方式执行事务,因此在run_search()部分完成之前无法重绘GUI。我环顾四周,解决方案是在不同的线程中进行搜索,或者在GUI上手动调用update()。后者被建议反对,无论如何都不适合我,大多数解决方案只是说“开始另一个线程”。我不知道如何做到这一点,甚至不确定一个线程是什么,除了它可能让代码并行运行。我试图复制一些使用进度条类的示例,但GUI仍然冻结了相同的内容。

这是所有相关的(我认为)代码

import player_search_engine
import tkinter as tk
import tkinter.ttk as ttk
import threading

class scouting_tool(tk.Tk):
    def __init__(self, parent):
        tk.Tk.__init__(self, parent)
        self.root = parent
        self.draw_search_panel()
        self.draw_results_panel()

    def draw_search_panel(self):
        ...

    def draw_results_panel(self):
        ...

    def draw_found_players(self):
        #displays a row showing the details of a player for every found player

    def search(self, event):
        #gets the input from all the search fields, applies them to the search engine settings,
        #and searches the database using them. Then displays all matching players

        #get the nationality from the combobox
        player_search_engine.target_nationality = self.nat_list.get()

        #get the inputs from the entry boxes
        player_search_engine.min_weight = self.get_entry_box_data(self.min_weight_box, 60)
        player_search_engine.max_weight = self.get_entry_box_data(self.max_weight_box, 160)
        ..etc 


        #run the search
        self.search_thread = searchThread()
        self.search_thread.start()
        self.search_done_check()


    def search_done_check(self): 
        if self.search_thread.done_event.is_set(): 
            self.draw_found_players() 
        else:
            super(scouting_tool, self).after(20, self.search_done_check)

class searchThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.done_event = threading.Event()

    def run(self):
        player_search_engine.search_database()  
        self.done_event.set()

app = scouting_tool(None)
app.title("BR Scout")
app.wm_resizable(False, False)
app.mainloop()

那么,如何为search_database()函数创建一个工作线程,以便GUI在函数运行时不会冻结?

2 个答案:

答案 0 :(得分:0)

我猜你的线程尝试并没有真正起作用,因为self.draw_found_players()等待线程执行的结束来绘制 玩家(所以实际上它与完全不使用线程相同)。

我建议您执行以下操作:

  • 在线程中搜索您的数据库,就像您尝试过的那样

  • 在你的线程类中
  • ,创建一个名为threading.Event的{​​{1}}对象:done_event

  • 在您的主self.done_event = threading.Event()方法中,启动Tkinter计时器以重复检查 如果线程完成,则不阻止GUI。添加search方法 像这样:

    def search_done_check(self):     如果search_thread.done_event.is_set():         self.draw_found_players()     其他:         self.root.after(100,self.search_done_check)

启动线程后,触发第一个search_done_check检查:

search_done

所以最后你将一个执行计算的线程与一个Tkinter计时器结合起来 检查是否完成,然后使用结果。

答案 1 :(得分:0)

假设数据库在同一台机器上,因此没有网络延迟。为了避免线程,将数据处理分成块并使用tk自己的调度程序之一,特别是.after_idle。代码大纲:

def start_process(args):  # command attached to button
    <open database, do other initialization
    root.after_idle(process_block, newargs)

def process_block(args):
  <do about 10 ms of work>
  root.after_idle(process_block, newargs)