Задать вопрос
@HEKOPEKTHbIU

Ошибка «cannot pickle '_tkinter.tkapp' object» при использовании Multiprocessing, почему?

Пишу программу с помощью графического модуля tkinter. Столкнулся с проблемой невозможности передачи виджетов в функции, которые запускаю в параллельном процессе через класс Process. Выдает ошибку "cannot pickle '_tkinter.tkapp' object". Тот же самый код нормально работает в библиотеке threading, но в ней нельзя принудительно остановить непрерывный процесс. Не понимаю почему в Thread все работает, а в Process - нет.
python 3.11.9 64bit Windows
tkinter Tcl/Tk 8.6.12

Python:
import time
import multiprocessing
from threading import Thread
from tkinter import *
from tkinter import ttk

# Модуль Multiprocessing и Thread, пока работает только так...
# Функция контроля работы потока расчета
def live(t1,w):
    while t1.is_alive():
        print("Ok")
        time.sleep(0.2)
    else:
        w.destroy()
# Функция счета
def s4et(k):
    i=0
    while i<10**6:
        i+=1
        d=i*k
        print(d)
# Функция открытия окна при расчете
def pusk():
    # Функция закрытия окна по нажатию кнопки
    def denide():
        t1.terminate()
        window.destroy()    # Закрытие окна
    # Новое вспомогательное окно
    window=Toplevel()
    window.geometry(f"{int(wtk/2)}x{int(htk/2)}+{int(w_wtk+wtk/4)}+{int(w_htk+htk/4)}")
    
    btn1=Button(master=window,text="Stop",cursor="hand2",border=4,command=denide,activebackground="white",activeforeground="black",bd=5,font=("Times","12"))
    btn1.pack(anchor='sw',pady=5,padx=0)
    t1=multiprocessing.Process(target=s4et,args=(2,),daemon=True) # Поток
    t2=Thread(target=live,args=(t1,window),daemon=True) # работает
    t3=multiprocessing.Process(target=live,args=(t1,window),daemon=True) # не работает
    t1.start() # Запуск потока расчета
    t2.start() # Запуск потока контроля
    t3.start() # Запуск потока контроля
    window.mainloop()

if __name__ == '__main__':
    root=Tk()
    # Размер и положение окна
    wtk=500
    htk=500
    w_wtk=root.winfo_screenwidth()          # Определение разрешения экрана по ширине
    w_htk=root.winfo_screenheight()         # Определение разрешения экрана по высоте
    w_wtk=w_wtk//2-wtk/2                      # Определение координат середины экрана по ширине
    w_htk=w_htk//2-htk/2                      # Определение координат середины экрана по высоте
    root.geometry(f"{wtk}x{htk}+{int(w_wtk)}+{int(w_htk)}")     # Размер и положение окна
    # Надписи
    lbl_res=Label(master=root,text="Запуск расчета")
    # Кнопка запуска расчета
    btn1=Button(master=root,text="ПУСК!",cursor="hand2",border=4,command=lambda:pusk(lbl_vivod),
                        activebackground="white",activeforeground="black",bd=5,font=("Times","12"))
    # Размещение виджетов
    lbl_res.pack(pady=5)
    btn1.pack(anchor='center',pady=50,padx=0)
    root.mainloop()
  • Вопрос задан
  • 54 просмотра
Подписаться 1 Простой 1 комментарий
Пригласить эксперта
Ответы на вопрос 2
Vindicar
@Vindicar
RTFM!
Потому что multiprocessing - это не магия, а инструмент с ограничениями.
В частности, у каждого дочернего процесса полностью своя память. Никаких общих объектов! Любые данные, передающиеся между процессами, упаковываются встроенным модулем pickle, и распаковываются на той стороне. Как следствие, есть довольно ограниченный набор типов данных, которые можно передавать как аргумент, или возвращать как результат из другого процесса. Окна ткинтера в этот набор не входят, о чём тебе и сообщает ошибка.

Используй пару multiprocessing.Queue или SimpleQueue для коммуникации с работающим процессом. Одна очередь должна содержать команды от главного процесса дочернему, а другая - оповещения от дочернего главному. Сосредоточь всю работу с GUI в главном процессе, а для периодической проверки наличия новых оповещений в очереди используй метод root.after().

Ну и да, "принудительно остановить" - это вообще не лучшая идея, и не должно быть нормальным подходом. Перепиши свои функции так, чтобы они мониторили какой-нибудь multiprocessing.Event (ну или threading.Event, если хочешь работать в потоках), и останавливались, когда он окажется взведён.
Ответ написан
Lord_of_Rings
@Lord_of_Rings
Дунадан - северный странник. Злой, но очень добрый
Дебильно, но работает
код
import time
import multiprocessing
from threading import Thread
from tkinter import *
from tkinter import ttk

# Функция контроля работы потока расчета


def monitor_process(calc_process, window):
    while calc_process.is_alive():
        print("Process is running")
        time.sleep(0.2)
    else:
        window.destroy()


# Функция выполнения расчета


def perform_calculation(multiplier):
    i = 0
    while i < 10**6:
        i += 1
        result = i * multiplier
        print(result)


# Функция открытия окна при расчете


def start_calculation(output_label):
    # Функция закрытия окна по нажатию кнопки
    def stop_calculation():
        calc_process.terminate()
        window.destroy()  # Закрытие окна

    # Новое вспомогательное окно
    window = Toplevel()
    window.geometry(
        f"{int(main_window_width / 2)}x{int(main_window_height / 2)}+{int(
            screen_width + main_window_width / 4)}+{int(screen_height + main_window_height / 4)}"
    )

    stop_button = Button(
        master=window,
        text="Stop",
        cursor="hand2",
        border=4,
        command=stop_calculation,
        activebackground="white",
        activeforeground="black",
        bd=5,
        font=("Times", "12"),
    )
    stop_button.pack(anchor="sw", pady=5, padx=0)

    calc_process = multiprocessing.Process(
        target=perform_calculation, args=(2,), daemon=True
    )  # Поток расчета
    monitor_thread = Thread(
        target=monitor_process, args=(calc_process, window), daemon=True
    )  # Поток контроля
    calc_process.start()  # Запуск потока расчета
    monitor_thread.start()  # Запуск потока контроля

    window.mainloop()


if __name__ == "__main__":
    root = Tk()
    # Размер и положение окна
    main_window_width = 500
    main_window_height = 500
    # Определение разрешения экрана по ширине
    screen_width = root.winfo_screenwidth()
    # Определение разрешения экрана по высоте
    screen_height = root.winfo_screenheight()
    # Определение координат середины экрана по ширине
    screen_width = screen_width // 2 - main_window_width // 2
    # Определение координат середины экрана по высоте
    screen_height = screen_height // 2 - main_window_height // 2
    root.geometry(
        f"{main_window_width}x{main_window_height}+{int(screen_width)
                                                    }+{int(screen_height)}"
    )  # Размер и положение окна

    # Надписи
    label_res = Label(master=root, text="Запуск расчета")
    output_label = Label(master=root, text="")

    # Кнопка запуска расчета
    start_button = Button(
        master=root,
        text="ПУСК!",
        cursor="hand2",
        border=4,
        command=lambda: start_calculation(output_label),
        activebackground="white",
        activeforeground="black",
        bd=5,
        font=("Times", "12"),
    )

    # Размещение виджетов
    label_res.pack(pady=5)
    start_button.pack(anchor="center", pady=50, padx=0)

    root.mainloop()
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы