2011-06-30 161 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循環運行速度非常快,並吸收了相當多的處理器週期,並等待輸入。似乎沒有微不足道的解決辦法。 –