多处理:带 GUI 的父/子(tkinter)

Posted

技术标签:

【中文标题】多处理:带 GUI 的父/子(tkinter)【英文标题】:Multiprocessing: Parent/Child w/ GUI (tkinter) 【发布时间】:2020-10-05 13:56:33 【问题描述】:

我正在创建一个带有 UI 的父程序,它产生多个子程序,也带有 GUI。所有子进程都需要与父进程对话(尝试 PIPE atm)。 Parent 使用 QUEUE 终止所有进程。

编辑:这些程序在 Raspberry Pi 4 w/ Raspbian OS 和 python 3.7.3 上运行。

主程序:bridge.py

import tkinter as tk
from time import sleep
import multiprocessing as mp
import os
import sys

import simple

class Main_Comm():
    def __init__(self):
        self.kill_queue = mp.Queue()
        self.p_conn, self.ch_conn = mp.Pipe()
        print("MAIN: PIPE: child: ".format(self.ch_conn))
        self.proc = mp.Process(target=simple.my_dev, \
                    args=(self.kill_queue, self.ch_conn, ))
        self.proc.start()

    def Stop_Devices(self):
        #self.kill_queue.put(True)
        self.proc.join()
        print("Wait for processes to finish ...")
        sleep(2)
        print("Device OFF!")

    def Info(self):
        print("Info: QUEUE , PIPE ".format(self.kill_queue, self.ch_conn))

class MainApp_bridge(tk.Tk):
    def __init__(self, master=None, title="Default"):
        #super().__init__()
        tk.Tk.__init__(self)
        self.title(title)
        self.btn = tk.Button(self, text="QUIT", command=self.on_quit)
        self.btn.pack(padx=20, pady=20)

        self.communicator = Main_Comm()
        self.communicator.Info()

        self.mainloop()

    def on_quit(self):
        print("Sending termination message via QUEUE ...")
        self.communicator.Stop_Devices()
        sleep(1)
        print("Shutting down main tread, HAL ...")
        sleep(1)
        self.destroy()

def main():
    root_bridge = MainApp_bridge(title="BRIDGE")

if __name__ == "__main__":
    main()

# EOF

和一个孩子(simple.py)

import tkinter as tk
import os
import random
from time import sleep
import sys

class MainApp_simple(tk.Tk):
    def __init__(self, parent=None, title="Device",
            FLAG=False, kq=None, chc=None):
        #super().__init__()
        tk.Tk.__init__(self)
        self.title(title)
        self.b_QUIT = tk.Button(self, text="QUIT", command=self.on_quit)
        self.b_QUIT.pack(side="top", padx=30, pady=30)
        self.window=self
        self.kq = kq
        self.chc = chc
        self.comm_agent = communicator( self.window, self.kq, self.chc )
        self.mainloop()

    def on_quit(self):
        print("CHILD: Quitting ...")
        self.destroy()

class Dev_comm():
    def __init__(self, win, kq, chc):
        self.kq = kq
        self.chc = chc
        self.win = win
        self.mypid = os.getpid()
        print("CHILD: PID is  and PIPE is ".format(self.mypid, chc))

def my_dev( kill_queue, child_comm ):
    root_simple = MainApp_simple(
            parent=None,
            title="CHILD", 
            FLAG=False, 
            kq=kill_queue, 
            chc=child_comm
            )

# EOF sim.py

每个程序都可以独立运行。如果我从桥上取出 GUI,它就可以工作。然而,总的来说,我得到了这个:

CHILD: MainApp - pre __init__ .... flushing
MAIN: PIPE: child: <multiprocessing.connection.Connection object at 0xb5989750>
Info: QUEUE <multiprocessing.queues.Queue object at 0xb5dc39d0>, PIPE <multiprocessing.connection.Connection object at 0xb5989750>
CHILD: Entered my_dev function ... flushing ...
XIO:  fatal IO error 25 (Inappropriate ioctl for device) on X server ":0.0"
      after 47 requests (47 known processed) with 2 events remaining.
X Error of failed request:  BadIDChoice (invalid resource ID chosen for this connection)
  Major opcode of failed request:  45 (X_OpenFont)
  Resource id in failed request:  0x2c00004
  Serial number of failed request:  114
  Current serial number in output stream:  120

我就是想不通!顺便说一句,flushing 没有提供任何新信息;错误信息以 XIO 开头 ...

首先我认为它与轮询管道和队列有关,干扰 mainloop() ...但显然不是。

非常感谢您的帮助。

干杯, 拉德克

编辑:我认为两个 tk.Tk 调用之间可能存在一些干扰,但只要父进程在终端中运行,我就可以使用 GUI 运行多个子进程。甚至管道和队列都可以工作......它是父 GUI......

【问题讨论】:

这是在什么平台上运行的?我怀疑这是multiprocessing 使用 start_method = "fork"; 的地方。尝试“spawn”可能会更好。在拨打任何 Tkinter 电话之前启动您的 Process 也可能有效。使用subprocess 而不是multiprocessing 肯定会让孩子使用它自己的GUI,但是你不再有一种方便的方式在孩子和父母之间交谈。 @jasonharper:RPi4 上的 Debian Linux。我记得读过 fork vs. spawn ...我会试一试。我也看到了 Popen 方法,但我不确定我是否可以使用管道和队列。嗯,我没有考虑在 GUI 之前进行处理......很有趣。但是,大多数操作将在 GUI 内进行。我试图让 GUI 与后端尽可能分离。 【参考方案1】:

Jason Harper 实际上提供了一个解决方案:SPAWN 而不是 FORK。

启动似乎有点慢,但一切都按预期运行。即使在进程之间轮询队列和管道也能很好地工作。

谢谢。 拉德克

【讨论】:

以上是关于多处理:带 GUI 的父/子(tkinter)的主要内容,如果未能解决你的问题,请参考以下文章

程序猿哥哥带你快速入门 Python GUI设计(tkinter)

tkinter学习1

Python GUI编程(Tkinter),python Tk()Frame()TopLevel()用法

Python GUI编程(Tkinter),python Tk()Frame()TopLevel()用法

GUI库之认识Tkinter

GUI的最终选择Tkinter模块初级篇