簡體   English   中英

使用多處理,從 PyQt5 應用程序停止子進程,其中子進程未使用事件循環

[英]Using multiprocessing, stopping a subprocess from an PyQt5 application, where the subprocess is not using an event loop

我正在構建一個運行 neural.networks 的 GUI 應用程序。 啟動它們工作正常,但我需要中止計算的能力,而 .net 已經在運行和工作。

我構建了一個小型原型,它向您展示了我的應用程序中問題的主要機制。

建築學

Window Class:

包含所有 GUI 元素,是啟動和控制一切的主要 Object。 它包含一個QThreadPool self.__poolQRunnable Object self.__runner QRunnable Object 包含 neural.net 所需的一切。 我使用 QRunnable object 的原因是,在另一個線程中處理 neural.net 時不阻塞 GUI。 我還需要在 neural.net 和我的應用程序之間進行通信。

跑步者 Class:
runner class 處理 Main Window 和 neural.net 本身之間的通信。 neural.net 是一個從 multiprocessing 放入 self._.net 的Process self._.net 對於通信,我使用Queue self.__queue 當啟動 QRunnable object 時,進程以self._.net.start()啟動。 我用無限循環觀察隊列。 循環在某些信號上終止。 在此示例中,我僅使用信號NetSignal.finished 當.net 完成后,我向 Window object 發送信號。

跑步者信號 Class:
由於 QRunnables 不能使用信號,所以需要這個 class 來 package 一些信號進入 QRuannable Object。

Class:
Mnist class 是 Subprocess 本身,繼承自Process 在這個例子中,它運行了一個非常簡單的例子,在 neural.network 中處理了 mnist 數據集。 進程周圍沒有循環,通常可用於停止此類子進程。 當 neural.network 完成時,隊列向 QRunnable object 發送信號,以通知進程已完成計算,因此向主 window 發送信號。

我需要能夠以某種方式停止該過程。 我想嘗試用os.kill將其殺死,這在我的應用程序中效果不佳。 我也試過self._.net.terminate()self._.net.kill() 我正在考慮以某種方式將 object 傳遞到 neural.net 的回調參數中,以便可能中止那里的處理,但我不確定它是否是通往 go 的方式。

代碼

from sys import exit, argv from multiprocessing import Process, Queue from PyQt5.QtWidgets import QPushButton, QApplication, QHBoxLayout, QWidget, QLabel from PyQt5.QtCore import QRunnable, QObject, pyqtSignal, QThreadPool class Window(QWidget): def __init__(self): QWidget.__init__(self) self.__btn_run = QPushButton("Start") self.__btn_stp = QPushButton("Stop") self.__label = QLabel("Idle") self.__runner = Runner() self.__pool = QThreadPool.globalInstance() self.__btn_run.clicked.connect(self.__run.net) self.__btn_stp.clicked.connect(self.__stp.net) self.__runner.signals.finished.connect(self.__on_finished) self.__btn_stp.setDisabled(True) self.setLayout(QHBoxLayout()) self.layout().addWidget(self.__btn_run) self.layout().addWidget(self.__btn_stp) self.layout().addWidget(self.__label) def __run.net(self): self.__btn_run.setDisabled(True) self.__btn_stp.setEnabled(True) self.__label.setText("Running") self.__pool.start(self.__runner) def __stp.net(self): pass # What to do here? def __on_finished(self): self.__btn_run.setEnabled(True) self.__btn_stp.setDisabled(True) self.__label.setText("Finished") self.__runner = Runner() class Runner(QRunnable): def __init__(self): QRunnable.__init__(self) self.__queue = Queue() self._.net = Mnist(self.__queue) self.signals = RunnerSignal() def run(self): self._.net.start() while True: data = self.__queue.get() if data == NetSignal.finished: self.signals.finished.emit() break class RunnerSignal(QObject): finished = pyqtSignal() class Mnist(Process): def __init__(self, queue: Queue): Process.__init__(self) self.__queue = queue def run(self): mnist = tf.keras.datasets.mnist # 28x28 Bilder hangeschriebener Ziffern von 0-9 (x_train, y_train), (x_test, y_test) = mnist.load_data() x_train = tf.keras.utils.normalize(x_train, axis=1) model = tf.keras.models.Sequential() model.add(tf.keras.layers.Flatten()) model.add(tf.keras.layers.Dense(128, activation=tf.nn.relu)) model.add(tf.keras.layers.Dense(128, activation=tf.nn.relu)) model.add(tf.keras.layers.Dense(10, activation=tf.nn.softmax)) model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=['accuracy']) model.fit(x_train, y_train, epochs=8) self.__queue.put(NetSignal.finished) self.__queue.close() class NetSignal: finished = "finished" if __name__ == "__main__": main_thread = QApplication(argv) main_window = Window() main_window.show() exit(main_thread.exec())

好的,現在我已經發現了問題所在。 當我用self._.net.terminate()終止進程時,仍然有僵屍進程剩余,為什么我認為這不是正確的解決方案。

僵屍進程是由未終止的 QRunnable 引起的,因為它正在等待子進程在隊列中發送信號。

因此,我需要一個自定義的“終止”function,它調用 terminate() 並向 QRunnable 發送一個信號,表明進程已完成。 這樣所有進程和線程都會相應地終止,並且不會留下任何僵屍進程。

import tensorflow as tf
from sys             import exit, argv
from multiprocessing import Process, Queue
from PyQt5.QtWidgets import QPushButton, QApplication, QHBoxLayout, QWidget, QLabel
from PyQt5.QtCore    import QRunnable, QObject, pyqtSignal, QThreadPool


class Window(QWidget):

    def __init__(self):
        QWidget.__init__(self)

        self.__btn_run = QPushButton("Start")
        self.__btn_stp = QPushButton("Stop")
        self.__label   = QLabel("Idle")

        self.__runner  = Runner()
        self.__pool    = QThreadPool.globalInstance()

        self.__btn_run.clicked.connect(self.__run_net)
        self.__btn_stp.clicked.connect(self.__stp_net)
        self.__runner.signals.finished.connect(self.__on_finished)

        self.__btn_stp.setDisabled(True)

        self.setLayout(QHBoxLayout())
        self.layout().addWidget(self.__btn_run)
        self.layout().addWidget(self.__btn_stp)
        self.layout().addWidget(self.__label)

    def __run_net(self):
        self.__btn_run.setDisabled(True)
        self.__btn_stp.setEnabled(True)
        self.__label.setText("Running")
        self.__pool.start(self.__runner)

    def __stp_net(self):
        self.__runner.close()
        # What to do here?

    def __on_finished(self):
        self.__btn_run.setEnabled(True)
        self.__btn_stp.setDisabled(True)
        self.__label.setText("Finished")
        self.__runner = Runner()


class Runner(QRunnable):

    def __init__(self):
        QRunnable.__init__(self)
        self.__queue = Queue()
        self.__net   = Mnist(self.__queue)
        self.signals = RunnerSignal()

    def run(self):
        self.__net.start()
        while True:
            data = self.__queue.get()
            if data == NetSignal.finished:
                self.signals.finished.emit()
                break

    def close(self):
        self.__net.end_process()


class RunnerSignal(QObject):
    finished = pyqtSignal()


class Mnist(Process):
    def __init__(self, queue: Queue):
        Process.__init__(self)
        self.__queue = queue

    def run(self):
        mnist = tf.keras.datasets.mnist  # 28x28 Bilder hangeschriebener Ziffern von 0-9

        (x_train, y_train), (x_test, y_test) = mnist.load_data()

        x_train = tf.keras.utils.normalize(x_train, axis=1)

        model   = tf.keras.models.Sequential()
        model.add(tf.keras.layers.Flatten())
        model.add(tf.keras.layers.Dense(128, activation=tf.nn.relu))
        model.add(tf.keras.layers.Dense(128, activation=tf.nn.relu))
        model.add(tf.keras.layers.Dense(10, activation=tf.nn.softmax))

        model.compile(optimizer="adam",
                      loss="sparse_categorical_crossentropy",
                      metrics=['accuracy'])

        model.fit(x_train, y_train, epochs=8)
        self.__queue.put(NetSignal.finished)
        self.__queue.close()

    def end_process(self):
        self.terminate()
        self.__queue.put(NetSignal.finished)
        self.__queue.close()
        

class NetSignal:
    finished = "finished"


if __name__ == "__main__":
    main_thread = QApplication(argv)
    main_window = Window()
    main_window.show()
    exit(main_thread.exec())

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM