如何从并行进程更新主窗口/画布。Python 3.12

0 投票
1 回答
46 浏览
提问于 2025-04-14 15:18

有一个程序可以实时模拟图的构建。为了更新进度并加快整个程序的运行速度,决定使用多进程。

但问题是,无法从一个并行的进程中更新主窗口或画布。

所以,也许有人遇到过类似的情况,或者知道怎么解决这个问题?

import time
from tkinter import *
from tkinter import ttk
from tkinter import filedialog
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import pandas as pd
import os
import sys
import inspect
import multiprocessing


def get_script_dir(follow_symlinks=True):
    if getattr(sys, 'frozen', False):
        path = os.path.abspath(sys.executable)
    else:
        path = inspect.getabsfile(get_script_dir)
    if follow_symlinks:
        path = os.path.realpath(path)
    return os.path.dirname(path)


def open_file():
    data_frame = ns.daf
    filepath = filedialog.askopenfilename(initialdir=get_script_dir())
    if filepath != "":
        data_frame = pd.read_csv(filepath, sep="\t")
    ns.daf = data_frame


def set_graf_data(nas, curva):
    # window = ns.window
    graf_value = [[] for _ in range(9)]
    x_value = [[] for _ in range(9)]
    data_frame = nas.daf
    axes = nas.axes
    delay = nas.delay

    line_object = [axes.plot([], [])[0] for _ in range(9)]
    print(data_frame)
    for i in range(len(data_frame[data_frame.columns.tolist()[1]])):
        if len(x_value[1]) == 1000:
            del x_value[1][0]
        x_value[1].append(float(data_frame[data_frame.columns.tolist()[1]].values[i]) * 86400 - float(data_frame[data_frame.columns.tolist()[1]].values[0]) * 86400)
        for j in range(9):
            if len(graf_value[j]) == 1000:
                del graf_value[j][0]

            graf_value[j].append(float(data_frame[data_frame.columns.tolist()[j + 2]].values[i]))

            line_object[j].set_data(x_value[1], graf_value[j])
        axes.relim()
        axes.autoscale()
        axes.set_xlim(x_value[1][-1] - 1000, x_value[1][-1] + 100)
        curva.update()
        time.sleep(1/delay)


def choose_click():
    data_frame = ns.daf
    choose_window = Tk()
    choose_window.columnconfigure(index=0, weight=1)
    choose_window.rowconfigure(index=0, weight=50)
    choose_window.rowconfigure(index=1, weight=1)
    choose_window.title("Choose")
    grafes = data_frame.columns.tolist()[2:]
    grafes_var = Variable(choose_window, value=grafes)
    grafes_listbox = Listbox(choose_window, listvariable=grafes_var)
    grafes_listbox.grid(row=0, column=0, sticky=NSEW)
    btn_choose = ttk.Button(choose_window, text="Confirm", cursor="hand2",
                            command=lambda: (choose_window.destroy()))
    btn_choose.grid(row=1, column=0, sticky=NSEW)
    ns.daf = data_frame


def confirm_time():
    d = float(entry_time.get())
    ns.delay = d


if __name__ == '__main__':

    root = Tk()
    root.title("Emulator")
    # root.geometry("250x200")

    fig, ax = plt.subplots(1, 1)
    plt.ion()

    canvas = FigureCanvasTkAgg(fig, master=root)
    canvas.get_tk_widget().grid(row=2, column=2, columnspan=3, rowspan=40, sticky=NSEW)

    delay_time = 10

    df = pd.DataFrame()

    mgr = multiprocessing.Manager()
    ns = mgr.Namespace()
    ns.daf = df
    ns.axes = ax
    ns.delay = delay_time
    # ns.window = root
    ns.can = canvas

    p1 = multiprocessing.Process(target=set_graf_data, args=(ns, canvas, ))

    btn_open = ttk.Button(text='Open file', command=open_file)
    btn_open.grid(column=0, row=0, sticky=NSEW)

    btn_check = ttk.Button(text='Check', command=choose_click)
    btn_check.grid(column=1, row=0, sticky=NSEW)

    btn_confirm_time = ttk.Button(text='Confirm time', command=confirm_time)
    btn_confirm_time.grid(row=1, column=1, sticky=NSEW)

    entry_time = ttk.Entry()
    entry_time.insert(0, str(1))
    entry_time.grid(row=1, column=0, sticky=E)

    btn_set_data = ttk.Button(text='Start', command=lambda: p1.start())
    btn_set_data.grid(column=2, row=0, sticky=NSEW)

    root.mainloop()

曾经尝试把整个窗口或单独的画布传递给并行进程,但出现了错误:

Traceback (most recent call last):
  File "C:\Users\egorov22\PycharmProjects\emulator\main.py", line 104, in <module>
    ns.can = canvas
    ^^^^^^
  File "C:\Users\egorov22\AppData\Local\Programs\Python\Python312\Lib\multiprocessing\managers.py", line 1129, in __setattr__
    return callmethod('__setattr__', (key, value))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\egorov22\AppData\Local\Programs\Python\Python312\Lib\multiprocessing\managers.py", line 820, in _callmethod
    conn.send((self._id, methodname, args, kwds))
  File "C:\Users\egorov22\AppData\Local\Programs\Python\Python312\Lib\multiprocessing\connection.py", line 206, in send
    self._send_bytes(_ForkingPickler.dumps(obj))
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\egorov22\AppData\Local\Programs\Python\Python312\Lib\multiprocessing\reduction.py", line 51, in dumps
    cls(buf, protocol).dump(obj)
TypeError: cannot pickle '_tkinter.tkapp' object

1 个回答

0

你不能在不同的进程之间使用或操作 tkinter 的组件。这是根本不可能的。

最简单的解决办法是设置一个多进程的 队列。工作进程可以在需要更新数据时把数据写入这个队列,而用户界面进程可以使用 after 方法定期检查这个队列,从中取出数据并更新界面。

你需要注意,检查队列的时间不要超过大约 100 毫秒,否则用户界面会显得很卡顿。

撰写回答