繁体   English   中英

从多处理计算更新 TKinter GUI

[英]Updating a TKinter GUI from a multiprocessing calculation

我正在为 python 模拟器创建一个GUI GUI提供了设置仿真和运行仿真的工具。 在模拟运行时,我想将进度信息传递给GUI ,并将其显示在我的simulation_frame中的Label上。 因为模拟需要通过多处理运行,所以我使用Queue将更新的信息传递回GUI

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

在我看来,这似乎是一个不雅且有潜在风险的解决方案。 此外,虽然它在Ubuntu中工作,但它似乎在Windows XP中不起作用——window 在运行一秒钟左右后变为空白。 我可以通过调用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

这可能对您有帮助,也可能没有帮助,但是可以通过确保在实例化根的特定线程上执行其代码和方法来使tkinter线程安全。 可以在Python Cookbook上找到一个试验过这一概念的项目,作为配方 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

如果您阅读该应用程序的 rest,您会发现它是使用定义为_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))

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM