python - Updating a TKinter GUI from a multiprocessing calculation -
i'm creating gui
python simulator. gui
provides tools set simulation , run it. while simulation running want pass progress information gui
, have displayed on label
in simulation_frame
. because simulations need run multiprocessing, i'm using queue
pass updated information gui
.
the way have set up, running simulations blocks tk
mainloop since need able close pool
@ end of call. i'm calling update_idletasks()
force gui
update progress information.
this seems me inelegant , potentially risky solution. moreover, while works in ubuntu
, not seem work in windows xp
--the window goes blank after second or of running. may able make work in windows
calling update()
rather update_idletasks()
, seems worse me.
is there better solution?
the relevant code:
sims = [] queues = [] svars = [] names = [] = 0 manager = mp.manager() config in self.configs: name, file, num = config.get() j = 0 _ 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)) += 1 j += 1 self.update() # progress tracking 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 in range(num_sims): q = queues[i] try: gen = q.get(timeout = .001) # if sim has updated, update label #print gen svars[i].set(gen) self.update() except empty: pass # results of map, if necessary tracker.get() def update(self): """ redraws """ self.master.update_idletasks() def run_1_sim(args): """ runs 1 simulation specified args, output updates 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 err: print err
this may or may not helpful you, possible make tkinter
thread-safe ensuring code , methods executed on particular thread root instantiated on. 1 project experimented concept can found on over python cookbook recipe 577633 (directory pruner 2). code below comes lines 76 - 253 , easy extend widgets.
primary thread-safety support
# 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 thread instance created on." __slots__ = '__action', '__thread' def __init__(self): "initialize affinityloop job queue , thread identity." self.__action = queue.queue() self.__thread = _thread.get_ident() def run(self, func, *args, **keywords): "run function on creating thread , 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 pending jobs in job queue." while not self.__action.empty(): job = self.__action.get_nowait() job.execute() ######################################################################## class __job: "store information run job @ later time." __slots__ = ('__func', '__args', '__keywords', '__error', '__mutex', '__value') def __init__(self, func, args, keywords): "initialize job's info , ready 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 job, store error, , return sender." try: self.__value = self.__func(*self.__args, **self.__keywords) except exception error: self.__error = true self.__value = error self.__mutex.release() @property def result(self): "return execution result or raise error." self.__mutex.acquire() if self.__error: raise self.__value return self.__value ################################################################################ class _threadsafe: "create thread-safe gui class safe cross-threaded calls." root = tkinter.tk def __init__(self, master=none, *args, **keywords): "initialize thread-safe wrapper around gui base class." if master none: if self.base not self.root: raise valueerror('widget must have master!') self.__job = affinityloop() # use affinity() if 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 later time if necessary." self.__obj = self.base(*args, **keywords) ######################################################################## # provide framework delaying method execution when needed. def __schedule(self, *args, **keywords): "schedule execution of method till later if necessary." return self.__job.run(self.__run, *args, **keywords) @classmethod def __run(cls, func, *args, **keywords): "execute function after converting arguments." args = tuple(cls.unwrap(i) in args) keywords = dict((k, cls.unwrap(v)) k, v in keywords.items()) return func(*args, **keywords) @staticmethod def unwrap(obj): "unpack inner objects wrapped _threadsafe instances." return obj.__obj if isinstance(obj, _threadsafe) else obj ######################################################################## # allow access , manipulation of wrapped instance's settings. def __getitem__(self, key): "get configuration option underlying object." return self.__schedule(operator.getitem, self, key) def __setitem__(self, key, value): "set configuration option on underlying object." return self.__schedule(operator.setitem, self, key, value) ######################################################################## # create attribute proxies methods , allow execution. def __getattr__(self, name): "create requested attribute , 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 attribute proxy." return self.__schedule(self.__method, path, *args, **keywords) def __method(self, path, *args, **keywords): "extract method , run provided arguments." method = self.__obj name in path: method = getattr(method, name) return method(*args, **keywords) ######################################################################## class __attr: "save attribute's name , wait execution." __slots__ = '__callback', '__path' def __init__(self, callback, path): "initialize proxy callback , method path." self.__callback = callback self.__path = path def __call__(self, *args, **keywords): "run known method given arguments." return self.__callback(self.__path, *args, **keywords) def __getattr__(self, name): "generate proxy object sub-attribute." if name in {'__func__', '__name__'}: # hack "tkinter.__init__.misc._register" method. raise attributeerror('this not real method!') return self.__class__(self.__callback, self.__path + (name,)) ################################################################################ # provide thread-safe classes used 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
if read rest of application, find built widgets defined _threadsafe
variants used seeing in other tkinter
applications. method calls come in various threads, automatically held until becomes possible execute calls on creating thread. note how mainloop
replaced way of lines 291 - 298 , 326 - 336.
notice nodefaltroot & main_loop calls
@classmethod def main(cls): "create application containing 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 allows threads execute
@staticmethod def main_loop(root): "process gui events according 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))
Comments
Post a Comment