There is a program that simulates the construction of a graph in real time. To update the schedule and speed up the work of the program as a whole, it was decided to use multiprocessing.
The problem is that there is no way to update the main window/canvas from a parallel process.
So, maybe someone has come across something like this or knows how to solve it?
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()
Были попытки передать в параллельный процесс само окно или отдельно canvas, но выскакивает ошибка:
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
You cannot use or interact with tkinter widgets across processes. That's simply not possible.
The simplest solution is to set up a multiprocessing queue. The worker process can write to the queue when there is data that needs to be updated, and the UI process can poll that queue using after
to pull the data off of the queue and update the UI.
You have to take care that your polling of the queue doesn't block the UI thread for more than 100ms or so, otherwise the UI will appear sluggish.