Python 从多处理计算更新TKinter GUI
我正在为python模拟器创建一个Python 从多处理计算更新TKinter GUI,python,tkinter,multiprocessing,Python,Tkinter,Multiprocessing,我正在为python模拟器创建一个GUI。GUI提供了设置和运行模拟的工具。模拟运行时,我希望将进度信息传递到GUI,并将其显示在我的simulation\u框架中的标签上。因为模拟需要使用多处理运行,所以我使用队列将更新的信息传递回GUI 按照我的设置方式,运行模拟会阻塞Tkmain循环,因为我需要能够在调用结束时关闭池。我正在调用update\u idletasks()以强制GUI更新进度信息 在我看来,这似乎是一个不雅观且有潜在风险的解决方案。此外,虽然它在Ubuntu中工作,但在wind
GUI
。GUI
提供了设置和运行模拟的工具。模拟运行时,我希望将进度信息传递到GUI
,并将其显示在我的simulation\u框架中的标签上。因为模拟需要使用多处理运行,所以我使用队列
将更新的信息传递回GUI
按照我的设置方式,运行模拟会阻塞Tk
main循环,因为我需要能够在调用结束时关闭池。我正在调用update\u idletasks()
以强制GUI
更新进度信息
在我看来,这似乎是一个不雅观且有潜在风险的解决方案。此外,虽然它在Ubuntu
中工作,但在windowsxp
中似乎不起作用——运行大约一秒钟后,窗口变为空白。我可以通过调用update()
而不是update\u idletasks()
使它在Windows
中工作,但这对我来说似乎更糟
有更好的解决办法吗
有关守则:
sims = []
queues = []
svars = []
names = []
i = 0
manager = mp.Manager()
for config in self.configs:
name, file, num = config.get()
j = 0
for _ in range(num):
#progress monitor label
q = manager.Queue()
s_var = StringVar()
label = Label(self.sim_frame, textvariable = s_var, bg = "white")
s_var.set("%d: Not Started"%i)
label.grid(row = i, column = 0, sticky = W+N)
self.sim_labels.append(label)
queues.append(q)
svars.append(s_var)
names.append("%s-%d"%(name, j))
sims.append(("%s-%d"%(name, j),file, data, verbose, q))
i += 1
j += 1
self.update()
# The progress tracking is pretty hacky.
pool = mp.Pool(parallel)
num_sims = len(sims)
#start simulating
tracker = pool.map_async(run_1_sim,sims)
while not tracker.ready():
pass
for i in range(num_sims):
q = queues[i]
try:
gen = q.get(timeout = .001)
# if the sim has updated, update the label
#print gen
svars[i].set(gen)
self.update()
except Empty:
pass
# The results of the map, if necessary
tracker.get()
def update(self):
"""
Redraws everything
"""
self.master.update_idletasks()
def run_1_sim(args):
"""
Runs one simulation with the specified args, output updates to the supplied
pipe every generation
"""
name,config,data, verbose, q = args
sim = Simulation(config, name=name, data = data)
generation = 0
q.put(sim.name + ": 0")
try:
while sim.run(verbose=verbose, log=True, generations = sim_step):
generation += sim_step
q.put(sim.name + ": " + str(generation))
except Exception as err:
print err
这可能对您有帮助,也可能没有帮助,但通过确保其代码和方法在根被实例化的特定线程上执行,可以使tkinter
线程安全。在as(目录剪枝器2)上可以找到一个试验该概念的项目。下面的代码来自第76-253行,很容易使用小部件进行扩展
主线程安全支持
# Import several GUI libraries.
import tkinter.ttk
import tkinter.filedialog
import tkinter.messagebox
# Import other needed modules.
import queue
import _thread
import operator
################################################################################
class AffinityLoop:
"Restricts code execution to thread that instance was created on."
__slots__ = '__action', '__thread'
def __init__(self):
"Initialize AffinityLoop with job queue and thread identity."
self.__action = queue.Queue()
self.__thread = _thread.get_ident()
def run(self, func, *args, **keywords):
"Run function on creating thread and return result."
if _thread.get_ident() == self.__thread:
self.__run_jobs()
return func(*args, **keywords)
else:
job = self.__Job(func, args, keywords)
self.__action.put_nowait(job)
return job.result
def __run_jobs(self):
"Run all pending jobs currently in the job queue."
while not self.__action.empty():
job = self.__action.get_nowait()
job.execute()
########################################################################
class __Job:
"Store information to run a job at a later time."
__slots__ = ('__func', '__args', '__keywords',
'__error', '__mutex', '__value')
def __init__(self, func, args, keywords):
"Initialize the job's info and ready for execution."
self.__func = func
self.__args = args
self.__keywords = keywords
self.__error = False
self.__mutex = _thread.allocate_lock()
self.__mutex.acquire()
def execute(self):
"Run the job, store any error, and return to sender."
try:
self.__value = self.__func(*self.__args, **self.__keywords)
except Exception as error:
self.__error = True
self.__value = error
self.__mutex.release()
@property
def result(self):
"Return execution result or raise an error."
self.__mutex.acquire()
if self.__error:
raise self.__value
return self.__value
################################################################################
class _ThreadSafe:
"Create a thread-safe GUI class for safe cross-threaded calls."
ROOT = tkinter.Tk
def __init__(self, master=None, *args, **keywords):
"Initialize a thread-safe wrapper around a GUI base class."
if master is None:
if self.BASE is not self.ROOT:
raise ValueError('Widget must have a master!')
self.__job = AffinityLoop() # Use Affinity() if it does not break.
self.__schedule(self.__initialize, *args, **keywords)
else:
self.master = master
self.__job = master.__job
self.__schedule(self.__initialize, master, *args, **keywords)
def __initialize(self, *args, **keywords):
"Delegate instance creation to later time if necessary."
self.__obj = self.BASE(*args, **keywords)
########################################################################
# Provide a framework for delaying method execution when needed.
def __schedule(self, *args, **keywords):
"Schedule execution of a method till later if necessary."
return self.__job.run(self.__run, *args, **keywords)
@classmethod
def __run(cls, func, *args, **keywords):
"Execute the function after converting the arguments."
args = tuple(cls.unwrap(i) for i in args)
keywords = dict((k, cls.unwrap(v)) for k, v in keywords.items())
return func(*args, **keywords)
@staticmethod
def unwrap(obj):
"Unpack inner objects wrapped by _ThreadSafe instances."
return obj.__obj if isinstance(obj, _ThreadSafe) else obj
########################################################################
# Allow access to and manipulation of wrapped instance's settings.
def __getitem__(self, key):
"Get a configuration option from the underlying object."
return self.__schedule(operator.getitem, self, key)
def __setitem__(self, key, value):
"Set a configuration option on the underlying object."
return self.__schedule(operator.setitem, self, key, value)
########################################################################
# Create attribute proxies for methods and allow their execution.
def __getattr__(self, name):
"Create a requested attribute and return cached result."
attr = self.__Attr(self.__callback, (name,))
setattr(self, name, attr)
return attr
def __callback(self, path, *args, **keywords):
"Schedule execution of named method from attribute proxy."
return self.__schedule(self.__method, path, *args, **keywords)
def __method(self, path, *args, **keywords):
"Extract a method and run it with the provided arguments."
method = self.__obj
for name in path:
method = getattr(method, name)
return method(*args, **keywords)
########################################################################
class __Attr:
"Save an attribute's name and wait for execution."
__slots__ = '__callback', '__path'
def __init__(self, callback, path):
"Initialize proxy with callback and method path."
self.__callback = callback
self.__path = path
def __call__(self, *args, **keywords):
"Run a known method with the given arguments."
return self.__callback(self.__path, *args, **keywords)
def __getattr__(self, name):
"Generate a proxy object for a sub-attribute."
if name in {'__func__', '__name__'}:
# Hack for the "tkinter.__init__.Misc._register" method.
raise AttributeError('This is not a real method!')
return self.__class__(self.__callback, self.__path + (name,))
################################################################################
# Provide thread-safe classes to be used from tkinter.
class Tk(_ThreadSafe): BASE = tkinter.Tk
class Frame(_ThreadSafe): BASE = tkinter.ttk.Frame
class Button(_ThreadSafe): BASE = tkinter.ttk.Button
class Entry(_ThreadSafe): BASE = tkinter.ttk.Entry
class Progressbar(_ThreadSafe): BASE = tkinter.ttk.Progressbar
class Treeview(_ThreadSafe): BASE = tkinter.ttk.Treeview
class Scrollbar(_ThreadSafe): BASE = tkinter.ttk.Scrollbar
class Sizegrip(_ThreadSafe): BASE = tkinter.ttk.Sizegrip
class Menu(_ThreadSafe): BASE = tkinter.Menu
class Directory(_ThreadSafe): BASE = tkinter.filedialog.Directory
class Message(_ThreadSafe): BASE = tkinter.messagebox.Message
如果阅读应用程序的其余部分,您会发现它是使用定义为\u ThreadSafe
变体的小部件构建的,这些小部件是您在其他tkinter
应用程序中经常看到的。当方法调用来自不同的线程时,它们会自动保持,直到可以在创建线程上执行这些调用为止。注意mainloop
是如何被第291-298行和第326-336行替换的
注意NoDefaltRoot和main_循环调用
@classmethod
def main(cls):
"Create an application containing a single TrimDirView widget."
tkinter.NoDefaultRoot()
root = cls.create_application_root()
cls.attach_window_icon(root, ICON)
view = cls.setup_class_instance(root)
cls.main_loop(root)
主循环允许线程执行
@staticmethod
def main_loop(root):
"Process all GUI events according to tkinter's settings."
target = time.clock()
while True:
try:
root.update()
except tkinter.TclError:
break
target += tkinter._tkinter.getbusywaitinterval() / 1000
time.sleep(max(target - time.clock(), 0))
我认为你是对的,创建我自己的“主循环”是一条路。这样,整个窗口都会更新,我可以拥有像“停止”按钮这样的时髦功能。我在寻找使Tkinter循环协作的方法时遇到了这个问题,如果我正确地阅读了最后一个片段,我认为它不会像预期的那样工作。.getbusywaitinterval()似乎返回一个int(=在我的解释器中为20),将其除以1000得到int 0。因此,除非.getbusywaitinterval()返回的值超过1000,否则target永远不会递增,我怀疑它通常会这样。修复很简单,只需将1000更改为1000.0以执行浮点计算,这将最终使目标值高于0,并实际让线程休眠。我编写了一个快速测试,发现它确实总是使用原始代码执行time.sleep(0)。不幸的是,修复此问题以执行适当的睡眠会导致睡眠在执行后的几秒钟内持续增加到1.5秒,这使得应用程序非常缓慢。保留原来的bug会使while循环运行得非常快,占用相当多的处理器周期,等待输入。似乎没有简单的修复方法。@IanE:Directory Pruner 4()使用了一种更好的方法在GUI上运行线程。这是最初引用的程序演变的一部分。@noctis skytower是一个更好的主循环:它睡眠时间合理(在运行程序的系统上通常为0.02秒),响应速度非常快。不幸的是,对于我的特定应用程序和其他应用程序,当用户按住按钮(OSX/Py2.7)时,self.update()不会返回。对于最初的问题,它可能已经足够了,只需注意,当用户操作UI时,UI可能不会更新。