在 Windows,Python3.8 上使用多处理时如何更新 tkinter 应用程序中的状态
Posted
技术标签:
【中文标题】在 Windows,Python3.8 上使用多处理时如何更新 tkinter 应用程序中的状态【英文标题】:how to update status in tkinter app when using multiprocessing on Windows, Python3.8 【发布时间】:2021-10-22 11:54:48 【问题描述】:我在文件 my_app.py
中有一个 tkinter GUI,在另一个文件 my_model.py
中有一个模型。该模型使用多处理进行一些计算。 MyModel
有一个外部循环,它在多处理之外。这个外部循环给出了程序中的步骤名称,所以我想使用这个名称来更新 GUI 中的标签。这样用户就可以看到当前状态。但是,当单击“运行”按钮时,GUI 冻结并且没有响应。只有当多处理完成后,才能再次使用 GUI,并且标签只显示最后一个(“名称 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')
【问题讨论】:
_loop_with_mp
的单次运行需要多长时间?还有Name 2
没有Name 1
显示的原因是因为代码执行不会回到tkinter
的主循环,所以它不能自我更新。尝试使用 tkinter
循环重写 for 循环,例如 this
MyModel 有一个在多处理之外的外部循环。我想这就是问题所在。如果主循环中的循环未完成,则主循环将无法继续,因此您的 GUI 将冻结。
【参考方案1】:
您的方法的问题是 Tk 应用程序冻结,而您正在等待模型的流程循环在 with Pool(processes=n_cpu) as loop
上下文管理器中完成执行任务。这是有关如何修复应用程序的完整工作示例:
#!/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()
注意,现在Model
类实例是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
函数调用。
【讨论】:
以上是关于在 Windows,Python3.8 上使用多处理时如何更新 tkinter 应用程序中的状态的主要内容,如果未能解决你的问题,请参考以下文章
windows上python3.8安装virtualenv遇到的一些问题
使用 python 3.8 在 windows 10 上安装 django 频道和扭曲的问题