本文将续上一章 Python GUI:Tkinter——05 ,没看过的同学可以看一下呀。
上一章中,我们已经将我们的 GUI 完善到比较精辟的程度。虽然控件、GUI 界面已经勉强差强人意了,但是里面的 bussiness logit 还是很傻瓜的。就线程方面来说,这个 GUI 属于单线程 application。那么,什么是线程?如何多线程?多线程有什么用?就是本章要学习的内容了。
线程与进程
进程即 process,线程为 thread。就咱们之前弄的 GUI 来说,他属于 单线程单进程。每当我们运行他的时候,就会在 windows 任务管理器中看到这个进程。然而,单一线程会让我们的 app 在运行长时间操作的时候,被冻住。
假设某个控件的触发函数,运行的时间比较长。那么你会发现,咱们这个 GUI 会原地“冻结”,使得我们之后的某些触发(你可以点鼠标)不会被响应。并常见的,会弹出一个“未响应”窗口叫你结束进程。
实际上,这就是单线程引起的。当我们的 GUI 被运行时,系统就会分配给你的 GUI 一个主线程。因此,如果主线程被占用,那么 GUI 也就被冻住了。所以,为了解决这个问题,我们需要给那些花费时间可能较长的触发函数,单独分配一个线程。还有另一种解决方式,就是给分配多 process。 但是进程之间通常不共享数据和代码,线程之间则相反。如果要让进程相互“沟通”的话,需要一种叫 IPC 的高端技术。而线程不用,一个进程中的多个线程,他们彼此共享代码和数据,因此不用操心 IPC 这一块。
如何多线程呢?下一节!
多线程实现
在 class OOP 下面 加入代码:
......
def methodInAThread(self):
print('你好吗?')
for idx in range(5): #模拟长时间触发事件
sleep(1)
self.aScrTxt.insert(tk.INSERT,str(idx)+'\n')
def createThread(self):
runT = Thread(target=self.methodInAThread) #创建一个线程,线程运行 methodInAThread
runT.setDaemon(True) #将线程设置成守护线程
runT.start()
......
什么叫守护线程呢?这是因为如果创建一个线程后,即便主线程关闭了,这个子线程也会继续运行下去,直到任务的终结。将其设置成守护线程,表示该线程是不重要的,进程退出时不需要等待这个线程执行完成。这样做的意义在于:避免子线程无限死循环,导致退不出程序,也就是避免传说中的孤儿进程。
更专业的说,setDaemon()设置为True, 则主线程执行完毕后会将子线程回收掉,设置为false,主进程执行结束时不会回收子线程。另外,其需要在 start 方法前调用;
队列
队列有什么用? 队列是 FIFO 的东西,即先进先出。他可以帮助我们实现多个输出任务的管理。
想来看看他的使用,首先修改程序:
from queue import Queue
续上
def methodInAThread(self):
print('你好吗?')
for idx in range(5):
sleep(1)
self.aScrTxt.insert(tk.INSERT,str(idx)+'\n')
sleep(1)
def createThread(self):
runT = Thread(target=self.methodInAThread)
runT.setDaemon(True)
runT.start()
writeT = Thread(target=self.useQueues,daemon=True)
writeT.start()
def useQueues(self):
guiQueue = Queue()
print(guiQueue)
for idx in range(10):
guiQueue.put('Message from a queue'+str(idx))
while True:
print(guiQueue.get())
言归正传,我们的队列用于解决什么问题呢?假设我们的 scrolledtext 控件要展示的信息,来源于多个不同的控件(不单单是一个 button 啦),这时候,我们就要用到 queue 来帮助我们管理这些信息来源了。
另外,queue 还能够帮助我们加强多个 .py 文件的交互,即实现多文本开发的解耦。
GUI 与 Business logic 分开
在开发一个 GUI 应用时,人们总想着把 GUI 控件定义部分、和控件逻辑功能部分的代码拆分开。也就是说,将 GUI 和 business logic 分开。如何分开,可以巧妙地利用 self 指针。
首先我们新建一个文件:logicpart.py,在下面输入代码:
def writeToScrol(inst):
print('hi from Queue',inst)
inst.createThread()
之后,我们就没必要在 clickBut 函数中调用 createThread 了,修改成如下即可:
def _clickBut(self):
self.aButton.configure(text='hello'+self.aEntry.get()+self.aCombobox.get())
# mBox.showerror('错误提示框','你没有错,是我错了')
# self.createThread()
logicpart.writeToScrol(self)
当然,这似乎并没有降低我们的麻烦。实际上,我们可以灵活使用 self 指针,从而将 methodInAThread 、createThread 什么的都给分开。怎么分开,请到评论区中一叙。
总结
本章介绍的内容短小精悍。在很多 GUI 中,都有打开一个对话框,然后选择文件这一操作。如何弄呢?请看下一章 Python GUI:Tkinter——07