在 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()
注意,现在Model
class实例是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
函数调用。
我在文件 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()
注意,现在Model
class实例是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
函数调用。