在 Windows、Python3.8 上使用多处理时如何更新 tkinter 应用程序中的状态

how to update status in tkinter app when using multiprocessing on Windows, Python3.8

我在文件 my_app.py 中有一个 tkinter GUI,在另一个文件 my_model.py 中有一个模型。该模型使用多处理进行一些计算。 MyModel 有一个在多处理之外的外循环。这个外部循环给出了程序中的步骤名称,所以我想使用这个名称来更新 GUI 中的标签。这样用户就可以看到当前状态。但是,当单击“运行”按钮时,GUI 冻结并且没有响应。只有当 multiprocessing 完成后,GUI 才能再次使用,标签只显示最后一个(“Name 2”)。你能帮帮我吗?

谢谢。

我在 Windows 系统上使用 Python 3.8.10。

# my_app.py

import tkinter as tk

from mp_model import MyModel


class MyApp:
    def __init__(self):
        self._root = tk.Tk()
        self.status = tk.StringVar()
        self.status.set('Status')
        self.label = tk.Label(self._root, textvariable=self.status)
        self.btn = tk.Button(self._root, text='Run', command=self.run_model)

        self.label.pack()
        self.btn.pack()

    def run(self):
        self._root.mainloop()

    def run_model(self):
        model = MyModel(status_var=self.status)
        model.run()


if __name__ == '__main__':
    app = MyApp()
    app.run()
# my_model.py

from multiprocessing import Pool
import time
from timeit import default_timer as timer
import multiprocessing as mp

import pandas as pd


def func_for_mp(name: str, ds_value: pd.Series) -> pd.Series:
    print(f'Doing {name}.')

    res_chunk = ds_value * 2.

    time.sleep(2)

    return res_chunk


class MyModel:
    def __init__(self, status_var=None):
        self.status_var = status_var

    def run(self):
        self._outer_loop()

    def _outer_loop(self):
        names = ['Name 1', 'Name 2']
        for name in names:
            self.status_var.set(name)
            self._loop_with_mp(name)

    def _loop_with_mp(self, name: str):
        all_values = pd.Series(range(35))

        n_cpu = mp.cpu_count()
        chunk_size = int(len(all_values) / n_cpu) + 1
        ds_chunks = [
            all_values.iloc[i:i+chunk_size] for i in range(0, len(all_values), chunk_size)
        ]

        start = timer()

        with Pool(processes=n_cpu) as pool:
            args = [(name, ds_chunk) for ds_chunk in ds_chunks]
            results = pool.starmap(func_for_mp, args)

        end = timer()
        print(f'Total elapsed time: {end - start}')

您的方法的问题在于,当您等待模型的进程循环完成 with Pool(processes=n_cpu) as loop 上下文管理器中的任务执行时,Tk 应用程序会冻结。这是有关如何修复该应用程序的完整工作示例:

#!/usr/bin/python3
import tkinter as tk

from model import Model

class App:

    def __init__(self):
        self._root = tk.Tk()
        self.status = tk.StringVar()
        self.status.set('Status')
        self.model = Model(status=self.status)
        self.label = tk.Label(self._root, textvariable=self.status)
        self.btn = tk.Button(self._root, text='Run', command=self.run_model)
        self.label.pack()
        self.btn.pack()

    def run(self):
        self._root.mainloop()
        print('Cleaning up...')
        self.model.cleanup()

    def run_model(self):
        self.model.run()


if __name__ == '__main__':
    app = App()
    app.run()

注意,现在Modelclass实例是App实例的变量。这是模型实现:

import time
import random
import multiprocessing as mp
from datetime import datetime


def task(name):
    print(f'Start task {name}')
    t_start = datetime.now()

    # Simulate a long-running task:
    time.sleep(random.randint(1, 5))

    t_elapsed = datetime.now() - t_start
    print(f'Done task {name} - Total elapsed time: {t_elapsed}')

    return name


class Model:

    def __init__(self, status):
        self.status = status
        self.pool = mp.Pool(processes=mp.cpu_count())

    def run(self):
        self._outer_loop()

    def _outer_loop(self):
        names = ['Name 1', 'Name 2']

        for name in names:
            self._loop_with_mp(name)

        self.status.set('Tasks submitted!')

    def _loop_with_mp(self, name):
        self.pool.apply_async(task, args=(name,), callback=self._task_done)

    def cleanup(self):
        self.pool.close()
        self.pool.join()

    def _task_done(self, name):
        self.status.set(f'Task {name} done!')

这里,池不是为每次执行创建的 运行,而是在创建 Model 实例时初始化的,因此您可以使用 pool.apply_async 异步提交任务方法。使用这种方法,您可以在下一个任务完成时使用提供给 pool.apply_async 函数调用。