QThread不是运行并行

QThread not running in parallel

我有一个函数 "preprocess" 可能需要很长时间才能执行。因此,我希望它 运行 与我的 UI 同时进行。我试着用 QThread 做这个:

class Preprocessor(QThread):
    done = pyqtSignal(ndarray)

    def __init__(self, image):
        super(Preprocessor, self).__init__()
        self.image = image

    def run(self) -> None:
        result = preprocess(self.image)
        self.done.emit(result)

然后 运行将其与:

preprocessor = Preprocessor(image)
preprocessor.start()
preprocessor.done.connect(self.handler_function)

但现在当我按下 UI 中的按钮时,它仍然只是 运行 我的预处理器并阻止 UI 响应。我无法更改有关预处理器代码的任何内容。

最小可重现示例:

import sys

import cv2
from PyQt5.Qt import *
from numpy.core.multiarray import ndarray
import dlib


class Preprocessor(QThread):
    done = pyqtSignal(ndarray)                 # ndarray

    def __init__(self, image):
        super(Preprocessor, self).__init__()
        self.detector = dlib.get_frontal_face_detector()
        self.image = image

    def run(self) -> None:
        gray = cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY)
        rects = self.detector(gray, 2)
        self.done.emit(self.image)

class ExampleApp(QMainWindow):
    def __init__(self):
        super().__init__()

        test_button = QPushButton(self)
        test_button.clicked.connect(self.start)
        self.preprocessor = None

    def handler_function(self, image):
        cv2.imshow('test', image)

    def start(self):
        image = cv2.imread("../Resources/snowIceLakeMountains.jpg")
        self.preprocessor = Preprocessor(image)
        self.preprocessor.done.connect(self.handler_function)
        self.preprocessor.start()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = ExampleApp()
    window.show()
    app.exec_()

好的,我想我知道为什么它不起作用以及如何解决它。 它不起作用,因为调用 rects = self.detector(gray, 2) 可能调用一些预编译的 C 代码,这是 Python 解释器的原子操作,因此它不能让其他线程执行操作之间的东西。 (我真的不知道,但这是一些 GIL 的东西)

我通过使用 multiprocessing.Process 和 multiprocessing.Queue

修复了它
import os
import queue
import sys
import cv2
import dlib

from multiprocessing import Queue, Process
from PyQt5.Qt import *
from numpy.core.multiarray import ndarray

class CheckDone(QThread):
    done = pyqtSignal(ndarray)                 # ndarray

    def __init__(self, proces_queue):
        super(CheckDone, self).__init__()
        self.process_queue = proces_queue

    def run(self) -> None:
        while True:
            try:
                image = self.process_queue.get(block=False)
                self.done.emit(image)
                return
            except queue.Empty:
                pass


def proc(q):
    path = os.path.dirname(os.path.abspath(__file__))
    image = cv2.imread(os.path.join(path, '..', 'Resources', 'snowIceLakeMountains.jpg'))
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    detector = dlib.get_frontal_face_detector()
    done = detector(gray, 2)
    q.put(image)


class ExampleApp(QMainWindow):
    def __init__(self, q):
        super().__init__()
        self.q = q
        self.test_button = QPushButton(self)
        self.test_button.clicked.connect(self.start)
        self.checker = None

    def handler_function(self, image):
        cv2.imshow('test', image)

    def start(self):
        p = Process(target=proc, args=(self.q, ))
        self.checker = CheckDone(self.q)
        self.checker.done.connect(self.handler_function)
        self.checker.start()
        p.start()


if __name__ == '__main__':
    pq = Queue()
    app = QApplication(sys.argv)
    window = ExampleApp(pq)
    window.show()
    app.exec_()