python - 使用 Tkinter 时如何并行化方法
问题描述
如果其中一个使用 Tkinter 对象,我如何同时运行两种方法?
问题设置:方法 A 在给定时间过去后停止方法 B。在此之前,方法 A 在 Tkinter 标签中显示剩余时间。
问题:方法不会同时运行。
Python版本: 2.7
操作系统: Windows 7
我使用线程来实现并发。我读过 Python 有一个叫做 Global Interpreter Lock 的东西,它可以让线程串行运行。我认为这会导致问题。
一种解决方法是使用进程。这是不可能的,因为 Tkinter 对象无法转换为字符流(“pickle”)。当我尝试使用 Processes 时出现此错误: PicklingError: Can't pickle 'tkapp' object。
下面的可运行示例模仿了更大的真实程序。出于这个原因,它使用模型-视图-控制器设计模式。我在函数调用中从 Timeout复制了一些代码。
用例:用户单击一个按钮。这将启动一个可能需要很长时间的后台任务。在后台任务运行的同时,前端会不断的通知用户,距离程序取消后台任务还有多少时间。
后台运行的线程未实现,因此可以停止。但这不是我想知道的。
from Tkinter import *
from time import sleep
from threading import Thread, Timer
class Frontend(Tk):
def __init__(self):
Tk.__init__(self)
self.label = Label(self, text = "", font = ("Courier", 12))
self.button = Button(self, text = "Run thread in background.", font = ("Courier", 12))
self.label.grid()
self.button.grid(sticky = "nsew")
class Backend:
def background_task(self):
print "Background task is executing."
sleep(6)
print "Finished."
class Controller:
def __init__(self):
self.INTERRUPT_AFTER = 4
self.done = True
self.backend = Backend()
self.frontend = Frontend()
self.frontend.button.configure(command = self.run_something_in_background)
class Decorator(object):
def __init__(self, instance, time):
self.instance = instance
self.time = time
def exit_after(self):
def outer(fn):
def inner():
timer = Timer(self.time, self.quit_function)
timer.start()
fn()
return timer
return inner
return outer
def quit_function(self):
if not self.instance.done:
self.instance.display_message("Interrupted background task.")
self.instance.set_done(True)
def run_something_in_background(self):
backendThread = Thread(target = self.backend.background_task)
decorator = self.Decorator(self, self.INTERRUPT_AFTER)
countdown = decorator.exit_after()(self.countdown) # exit_after returns the function with which we decorate.
self.set_done(False)
countdown()
backendThread.start()
backendThread.join()
self.set_done(True)
def countdown(self):
seconds = self.INTERRUPT_AFTER
while seconds > 0 and not self.done:
message = "Interrupting background task in {} seconds\nif not finished.".format(str(seconds))
self.display_message(message)
seconds -= 1
sleep(1)
def set_done(self, val):
self.done = val
def display_message(self, message):
self.frontend.label.config(text = message)
self.frontend.update_idletasks()
def run(self):
self.frontend.mainloop()
app = Controller()
app.run()
解决方案
尝试使用线程或多处理将面临的挑战是 tkinter 事件循环似乎受到后端线程/进程的影响。你能做的就是我在这里所做的。关键是使用 subprocess.Popen()。这会强制解释器打开另一个没有加载 tkinter 并且没有运行主循环的解释器(确保你没有)。
这是 frontend.py 程序:
from Tkinter import *
from subprocess import Popen
class Frontend(Tk):
def __init__(self):
Tk.__init__(self)
self.label = Label(self, text = "", font = ("Courier", 12), justify='left')
self.button = Button(self, text = "Run thread in background.", font = ("Courier", 12))
self.label.grid()
self.button.grid(sticky = "nsew")
class Controller:
def __init__(self):
self.INTERRUPT_AFTER = 4
self.done = False
self.frontend = Frontend()
self.frontend.button.configure(command = self.run_something_in_background)
def run_something_in_background(self, *args):
self.set_done(False)
seconds = 4
self.frontend.after(int(seconds) * 1000, self.stopBackend)
self.countdown(seconds)
self.backendProcess = Popen(['python', 'backend.py'])
def stopBackend(self):
self.backendProcess.terminate()
self.done = True
print 'Backend process terminated by frontend.'
self.display_message('Backend process terminated')
def countdown(self, remaining):
print 'countdown', remaining
if remaining > 0:
message = "Interrupting background task in"
message += " {} seconds\nif not finished.".format(str(remaining))
elif self.done:
message = 'Backend process completed'
self.display_message(message)
remaining -= 1
if remaining > 0 and not self.done:
self.frontend.after(1000, lambda s=remaining: self.countdown(s))
else:
message = 'Interrupting backend process.'
self.display_message(message)
def set_done(self, val):
self.done = val
def display_message(self, message):
self.frontend.label.config(text = message)
self.frontend.update_idletasks()
def run(self):
self.frontend.mainloop()
app = Controller()
app.run()
和 backend.py 代码:
from time import sleep
def background_task():
print "Background task is executing."
for i in range(8):
sleep(1)
print 'Background process completed', i+1, 'iteration(s)'
print "Finished."
background_task()
推荐阅读
- c# - 当您使用 asp.net 身份有单独的身份项目时如何在其他项目中验证 JWT 令牌
- javascript - 从 AngularJS 中的 Promise.then() 返回 API 响应
- php - moyasar 支付 API 出现问题
- node.js - 从 Node.js 中的函数中获取价值
- android - 具有固定页眉、可滚动正文和固定页脚的 DialogFragment
- python-3.x - 是什么导致“非哈希类型”错误?
- android - onRestart 状态的目的是什么?
- node.js - 如何在不更改的情况下更新数据?
- sql - 在 where 语句中使用 if
- php - 如何使用复选框多次插入数据