Обновление GUI TKinter из расчета многопроцессорности

Я создаю GUI для симулятора python. GUI предоставляет инструменты для настройки имитации и запуска. Пока выполняется симуляция, я хочу передать информацию о ходе работы в GUI и показать ее на Label в моей simulation_frame . Поскольку симуляции нужно запускать с многопроцессорной обработкой, я использую Queue для передачи обновленной информации в GUI .

Способ, которым я его настроил, запускает симуляции, блокирует главный шлюз Tk так как мне нужно закрыть мой Pool в конце вызова. Я update_idletasks() чтобы заставить GUI обновлять информацию о ходе.

Это кажется мне нелепым и потенциально рискованным решением. Более того, несмотря на то, что он работает в Ubuntu , он, похоже, не работает в Windows XP Окно заканчивается через секунду или около того. Я могу заставить его работать в Windows , вызывая update() а не update_idletasks() , но мне это кажется еще хуже.

Есть ли лучшее решение?

Соответствующий код:

 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 модели 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 

One Solution collect form web for “Обновление GUI TKinter из расчета многопроцессорности”

Это может быть или не быть полезным для вас, но можно сделать tkinter поток tkinter безопасным, гарантируя, что его код и методы выполняются в конкретном потоке, на котором был создан root. Один проект, который экспериментировал с концепцией, можно найти в Поваренной книге Python в качестве рецепта 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)) 

  • Утечка в обновлении этикетки
  • В tkinter python, как я могу создать ярлык, чтобы вы могли выбрать текст с помощью мыши?
  • Изменение слов в кнопках сообщений tkinter
  • Список всех событий Tkinter
  • python tkinter с потоковой обработкой
  • Скрытие диалогового окна командной строки в py2exe
  • Python Tkinter: выбор дерева
  • Какая магия предотвращает блокировку программ Tkinter в интерактивной оболочке?
  • Python - лучший язык программирования в мире.