2011-06-30 160 views
3

我正在为python模拟器创建GUIGUI提供了一些工具来设置模拟并运行它。在模拟运行时,我想将进度信息传递给GUI,并将其显示在我的simulation_frameLabel上。由于模拟需要使用多处理功能运行,因此我使用Queue将更新的信息传递回GUI从多处理计算更新TKinter GUI

我设置它的方式,运行模拟块阻止Tk主循环,因为我需要能够在通话结束时关闭我的Pool。我打电话update_idletasks()强制GUI更新进度信息。

在我看来,这似乎是一种不合理的,潜在风险的解决方案。此外,虽然它在Ubuntu工作,它似乎并没有在Windows XP工作 - 窗口后大约一秒钟左右的运行。我可以通过调用update()而不是update_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 

回答

2

这可能会或可能不会对你有帮助,但它有可能使tkinter线程安全的,确保其代码和方法是在特定线程的根上执行被实例化。可以在Python Cookbook上找到一个用该概念进行实验的项目,名称为recipe 577633(Directory Pruner 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 

如果你读了应用程序的其余部分,你会发现,它是建立与定义为_ThreadSafe变种,你是小部件用于在其他tkinter应用程序中看到。当方法调用从各个线程进入时,它们会自动保持,直到可以在创建线程上执行这些调用。注意mainloop是如何通过线的方式取代291 - 298和326 - 336


通知NoDefaltRoot &中main_loop呼吁

@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) 

中main_loop允许线程执行

@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)) 

+1

我认为你是对的,创建我自己的“主循环”是要走的路。这样整个窗口更新,我可以拥有“停止”按钮等时髦功能。 – Evlutte

+0

遇到此问题寻找使Tkinter循环合作的方法,如果我正确阅读最后一个代码段,我认为它不会按预期工作。 .getbusywaitinterval()似乎在我的解释器中返回一个int(= 20),并将其除以1000得到int 0.因此,除非.getbusywaitinterval()返回超过1000的任何东西,否则target永远不会增加,我怀疑它通常会如此。 修复很简单,只需将1000更改为1000.0即可执行浮点计算,最终将目标值增加到高于0的值,并且实际上会让线程休眠。 –

+0

我写了一个快速测试,发现它总是执行一个time.sleep(0)与原始代码。不幸的是,修复此问题以执行适当的睡眠会导致睡眠持续增加,在执行后的几秒钟内达到1.5秒,这使应用程序真的很慢。离开原来的bug使得while循环运行速度非常快,并吸收了相当多的处理器周期,并等待输入。似乎没有微不足道的解决办法。 –