python多线程处理tkinter用户操作的后台事件,解决tkinter界面卡死无响应的问题

文章目录

Python在使用tkinter库制作图形化界面的时候,‘点击按钮-事件处理-显示结果’是很常见的操作,当程序遇到一个比较耗时的事件时,界面就会卡死无响应。
未响应的tk窗口
问题出现的原因在于图形化界面的本质是一个循环,通过不断刷新界面来实现事件的实时响应和更新,如果有一个事件耗时过长,就会导致循环阻塞,界面无法刷新,窗口就会无响应。

那么该如何解决?

这是一个非常简单的界面,当点击按钮时,文本框会自动填入文本
简易的按钮响应事件

from tkinter import *
class Main():
    def __init__(self):
        root = Tk()
        self.entry = Entry(root)
        self.button = Button(root, text='执行',command=self.do_something)
        self.grid()
        root.mainloop()
    def grid(self):
        self.entry.grid(row=0,column=0)
        self.button.grid(row=0,column=1)
    def do_something(self):
        self.entry.insert('0', '结束了')
if __name__ == '__main__':
    Main()

现在,我需要让事件执行的时间长一些,使用sleep强制等待来模拟事件执行。

    def do_something(self):
        sleep(10)
        self.entry.insert('0', '结束了')

点击按钮10秒后,文本框填入“结束了”。
但是窗口无法移动,无法操作,甚至无响应。
导入Python线程库Threading,定义一个生成子线程的函数,并由子线程来触发事件

    def start_thread(self):
    	# 使self.do_something函数在子线程中运行
        insert_data = threading.Thread(target=self.do_something)
        insert_data.start()

然后,我们就可以通过调用该函数生成子线程并由子线程完成事件,所以按钮的command参数应该传入这个线程函数

self.button = Button(root, text='执行',command=self.start_thread)

如果还需要传参,可以将参数放在线程函数中或__init__中

    def do_something(self,data):
        sleep(10)
        self.entry.insert('0', data)
    def start_thread(self):
        data = '结束了'
        insert_data = threading.Thread(
        				target=self.do_something,
        				args=(data,))
        insert_data.start()
if __name__ == '__main__':
    Main()

但是,我觉得这并不是很方便,在按钮和事件中插入了第三者,而这个线程函数并不复杂,只有传参和调用,于是我们可以应用lambda替换它。

self.button = Button(root, text='执行',
			command=lambda :threading.Thread(
			target=self.do_something,args=('结束了',)
			).start())

总结

当按钮事件耗时较长时,我们可以在原来’的按钮->事件’间插入子线程,变成’按钮->子线程,子线程->事件’,从而实现子线程处理事件;当子线程函数很简单时,用lambda替换。

——————————————————————————
以下是完整代码:(倒计时10秒)

import threading
from time import sleep
from tkinter import *
class Main():
    def __init__(self):
        root = Tk()
        self.entry = Entry(root)
        time = 10
        self.button = Button(root, text='执行',
                             command=lambda :threading.Thread(
                                 target=self.do_something,
                                 args=(time,)
                             ).start())
        self.grid()
        root.mainloop()
    def grid(self):
        self.entry.grid(row=0,column=0)
        self.button.grid(row=0,column=1)
    def do_something(self,data):
        for i in range(data,0,-1):
            self.entry.insert('0', f'倒计时{i}秒')
            sleep(1)
            self.entry.delete('0',END)
        else:
            self.entry.insert('0', '倒计时结束')
if __name__ == '__main__':
    Main()

版权声明:本文为qq_51188076原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。