如果其中一个方法正在使用Tkinter对象,如何同时运行两个方法?
问题设置:方法A在给定时间过后停止方法B.在此之前,方法A在Tkinter标签中显示剩余时间。
问题:方法不会同时运行。
Python版本: 2.7
操作系统:Windows 7
我使用Threads来实现并发。我已经读过Python有一个名为Global Interpreter Lock的东西,它使线程串行运行。我认为这会导致问题。
解决方法是使用进程。这是不可能的,因为Tkinter对象不能转换为字符流(“pickle”)。当我尝试使用进程时出现此错误: PicklingError:无法挑选'tkapp'对象。
下面的可运行示例模仿了更大的真实程序。因此,它使用了模型 - 视图 - 控制器设计模式。我复制了Timeout on a function call的一些代码。
使用案例:用户点击按钮。这会启动一个可能需要很长时间的后台任务。当后台任务正在运行时,前端会不断通知用户在程序取消后台任务之前还剩多少时间。
在后台运行的线程未实现,因此可以停止。但这不是我想知道的。
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()
答案 0 :(得分:1)
您尝试使用线程或多处理的挑战是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()