схемы многопроцессорного графического интерфейса для борьбы с блокировкой «Не отвечает»

Каковы наилучшие способы создания многопроцессорной системы кодирования с графическим интерфейсом?

Я хотел бы создать место, где интернет-сообщество могло бы найти примеры использования модуля multiprocessing в python.

Я видел в Интернете несколько небольших примеров multiprocessing процессов простых глобальных функций, которые вызываются в основном модуле, но я обнаружил, что это редко легко преобразуется во что-либо, что кто-либо на самом деле делает в отношении графических интерфейсов. Я бы подумал, что многие программы будут иметь функции, которые они хотят использовать в отдельном процессе в качестве методов объектов (которые могут быть агрегатами других объектов и т. д.), и, возможно, один элемент графического интерфейса будет иметь связанный объект, который должен вызывать это процесс и др.

Например, у меня есть относительно сложная программа, и у меня возникают проблемы с созданием адаптивного графического интерфейса для нее, что, как я полагал, связано с моим непониманием в multiprocessing и многопоточности с QThread. Тем не менее, я знаю, что пример, приведенный ниже, по крайней мере, будет передавать информацию между процессами так, как я хочу (из-за возможности выполнять операторы print), но мой графический интерфейс все еще блокируется. Кто-нибудь знает, что может быть причиной этого, и если это все еще проблема с моим непониманием многопоточных/многопроцессорных архитектур?

Вот небольшой пример псевдокода того, что я делаю:

class Worker:
    ...
    def processing(self, queue):
        # put stuff into queue in a loop

# This thread gets data from Worker
class Worker_thread(QThread):
    def __init__(self):
        ...
        # make process with Worker inside
    def start_processing(self):
        # continuously get data from Worker
        # send data to Tab object with signals/slots

class Tab(QTabWidget):
    # spawn a thread separate from main GUI thread

    # update GUI using slot
    def update_GUI()

И этот код является полностью компилируемым примером, который воплощает основную структуру моей программы:

from PyQt4 import QtCore, QtGui
import multiprocessing as mp
import numpy as np
import sys
import time

# This object can hold several properties which will be used for the processing
# and will be run in the background, while it updates a thread with all of it's progress
class Worker:
    def __init__(self, some_var):
        self.some_var = some_var
        self.iteration = 0

    def some_complex_processing(self, queue):
        for i in range(0,5000):
            self.iteration += 1
            queue.put(self.iteration)
        queue.put('done with processing')

# This Woker_thread is a thread which will spawn a separate process (Worker).
# This separate is needed in order to separate the data retrieval
# from the main GUI thread, which should only quickly update when needed 
class Worker_thread(QtCore.QThread):
    # signals and slots are used to communicate back to the main GUI thread
    update_signal = QtCore.pyqtSignal(int)
    done_signal = QtCore.pyqtSignal()

    def __init__(self, parent, worker):
        QtCore.QThread.__init__(self, parent)
        self.queue = mp.Queue()
        self.worker = worker
        self.parent = parent
        self.process = mp.Process(target=self.worker.some_complex_processing, args=(self.queue,))

    # When the process button is pressed, this function will start getting data from Worker
    # this data is then retrieved by the queue and pushed through a signal
    # to Tab.update_GUI
    @QtCore.pyqtSlot()
    def start_computation(self):
        self.process.start()
        while(True):
            try:
                message = self.queue.get()
                self.update_signal.emit(message)
            except EOFError:
                pass
            if message == 'done with processing':
                self.done_signal.emit()
                break
            #self.parent.update_GUI(message)
        self.process.join()
        return

# Each tab will start it's own thread, which will spawn a process
class Tab(QtGui.QTabWidget):
    start_comp = QtCore.pyqtSignal()
    def __init__(self, parent, this_worker):
        self.parent = parent
        self.this_worker = this_worker
        QtGui.QTabWidget.__init__(self, parent)

        self.treeWidget = QtGui.QTreeWidget(self)
        self.properties = QtGui.QTreeWidgetItem(self.treeWidget, ["Properties"])
        self.step = QtGui.QTreeWidgetItem(self.properties, ["Iteration #"])

        self.thread = Worker_thread(parent=self, worker=self.this_worker)
        self.thread.update_signal.connect(self.update_GUI)
        self.thread.done_signal.connect(self.thread.quit)
        self.start_comp.connect(self.thread.start_computation)
        self.thread.start()

    ###############################
    # Here is what should update the GUI at every iteration of Worker.some_complex_processing()
    # The message appears to be getting sent, due to seeing the print statement in the console, but the GUI is not updated.
    @QtCore.pyqtSlot(int)
    def update_GUI(self, iteration):
        self.step.setText(0, str(iteration))
        #time.sleep(0.1)
        print iteration

    def start_signal_emit(self):
        self.start_comp.emit()

# GUI stuff
class MainWindow(QtGui.QMainWindow):
    def __init__(self, parent = None):
        QtGui.QMainWindow.__init__(self)
        self.tab_list = []
        self.setTabShape(QtGui.QTabWidget.Rounded)
        self.centralwidget = QtGui.QWidget(self)
        self.top_level_layout = QtGui.QGridLayout(self.centralwidget)

        self.tabWidget = QtGui.QTabWidget(self.centralwidget)
        self.top_level_layout.addWidget(self.tabWidget, 1, 0, 25, 25)

        process_button = QtGui.QPushButton("Process")
        self.top_level_layout.addWidget(process_button, 0, 1)
        QtCore.QObject.connect(process_button, QtCore.SIGNAL("clicked()"), self.process)

        self.setCentralWidget(self.centralwidget)
        self.centralwidget.setLayout(self.top_level_layout)

        # Make Tabs in loop from button
        for i in range(0,10):
            name = 'tab' + str(i)
            self.tab_list.append(Tab(self.tabWidget, Worker(name)))
            self.tabWidget.addTab(self.tab_list[-1], name)

    # Do the processing
    def process(self):
        for tab in self.tab_list:
            tab.start_signal_emit()
        return

if __name__ == "__main__":
    app = QtGui.QApplication([])
    win = MainWindow()
    win.show()
    sys.exit(app.exec_())

Дополнительная информация: Я пишу программу, из которой я хотел бы создать несколько процессов, чтобы они постоянно отображали свой прогресс во время обработки. Я хотел бы, чтобы программа была многопроцессорной, чтобы получить максимальную скорость от программы.

На данный момент я пытаюсь использовать поток для создания процесса и использовать сигналы и слоты для обновления графического интерфейса, в то время как данные постоянно извлекаются из очереди. Похоже, что queues, signals и slots работают при использовании операторов print, но не могут обновить графический интерфейс. Если у кого-то есть какие-либо другие предложения относительно того, как я должен структурировать это, чтобы сделать программу более управляемой, я хотел бы узнать.

РЕДАКТИРОВАТЬ: я внес коррективы, предложенные Мин Лином, с добавлением преобразования Worker в QObject, чтобы moveToThread() работало.
Вот новый код, который я есть на данный момент:

from PyQt4 import QtCore, QtGui
import multiprocessing as mp
import numpy as np
import sys
import time

class Worker(QtCore.QObject):
    update_signal = QtCore.pyqtSignal(int)
    done_signal = QtCore.pyqtSignal()

    def __init__(self, some_var):
        QtCore.QObject.__init__(self, parent=None)
        self.some_var = some_var
        self.iteration = 0
        self.queue = mp.Queue()
        self.process = mp.Process(target=self.some_complex_processing, args=(self.queue,))

    def some_complex_processing(self, queue):
        for i in range(0,5000):
            self.iteration += 1
            queue.put(self.iteration)
        queue.put('done with processing')

    @QtCore.pyqtSlot()
    def start_computation(self):
        self.process.start()
        while(True):
            try:
                message = self.queue.get()
                self.update_signal.emit(message)
            except EOFError:
                pass
            if message == 'done with processing':
                self.done_signal.emit()
                break
        self.process.join()
        return



class Tab(QtGui.QTabWidget):
    start_comp = QtCore.pyqtSignal()
    def __init__(self, parent, this_worker):
        self.parent = parent
        self.this_worker = this_worker
        QtGui.QTabWidget.__init__(self, parent)

        self.treeWidget = QtGui.QTreeWidget(self)
        self.properties = QtGui.QTreeWidgetItem(self.treeWidget, ["Properties"])
        self.step = QtGui.QTreeWidgetItem(self.properties, ["Iteration #"])

        # Use QThread is enough
        self.thread = QtCore.QThread();
        # Change the thread affinity of worker to self.thread.
        self.this_worker.moveToThread(self.thread);
        self.this_worker.update_signal.connect(self.update_GUI)
        self.this_worker.done_signal.connect(self.thread.quit)
        self.start_comp.connect(self.this_worker.start_computation)
        self.thread.start()

    ###############################
    # Here is what should update the GUI at every iteration of Worker.some_complex_processing()
    # The message appears to be getting sent, due to seeing the print statement in the console, but the GUI is not updated.
    @QtCore.pyqtSlot(int)
    def update_GUI(self, iteration):
        self.step.setText(0, str(iteration))
        #time.sleep(0.1)
        print iteration

    def start_signal_emit(self):
        self.start_comp.emit()

# GUI stuff
class MainWindow(QtGui.QMainWindow):
    def __init__(self, parent = None):
        QtGui.QMainWindow.__init__(self)
        self.tab_list = []
        self.setTabShape(QtGui.QTabWidget.Rounded)
        self.centralwidget = QtGui.QWidget(self)
        self.top_level_layout = QtGui.QGridLayout(self.centralwidget)

        self.tabWidget = QtGui.QTabWidget(self.centralwidget)
        self.top_level_layout.addWidget(self.tabWidget, 1, 0, 25, 25)

        process_button = QtGui.QPushButton("Process")
        self.top_level_layout.addWidget(process_button, 0, 1)
        QtCore.QObject.connect(process_button, QtCore.SIGNAL("clicked()"), self.process)

        self.setCentralWidget(self.centralwidget)
        self.centralwidget.setLayout(self.top_level_layout)

        # Make Tabs in loop from button
        for i in range(0,10):
            name = 'tab' + str(i)
            self.tab_list.append(Tab(self.tabWidget, Worker(name)))
            self.tabWidget.addTab(self.tab_list[-1], name)

    # Do the processing
    def process(self):
        for tab in self.tab_list:
            tab.start_signal_emit()
        return

if __name__ == "__main__":
    app = QtGui.QApplication([])
    win = MainWindow()
    win.show()
    sys.exit(app.exec_())

Спасибо за все ответы, я ценю уровень детализации, с которым все описывали идею, которую они считают решением, но, к сожалению, я еще не смог выполнить эти типы процессов, которые работают с объектом, к которому они принадлежат. при отображении атрибута объекта в графическом интерфейсе.
Тем не менее, я многому научился из этого поста, что позволило мне понять, что версия с многопотоковой связью, которая у меня есть на данный момент, зависает в графическом интерфейсе, поскольку функция обновления графического интерфейса слишком большой и требует слишком много обработки.

Итак, я применил подход QTimer() к своей многопоточной версии, и она работает намного лучше! Я бы посоветовал всем, кто сталкивается с подобными проблемами, хотя бы попробовать что-то подобное.

Я не знал об этом подходе к решению проблем с обновлением графического интерфейса, и теперь это псевдо или временное решение проблемы, с которой я столкнулся.


person chase    schedule 29.03.2013    source источник
comment
Мой совет — попытаться еще больше упростить ваш пример. Попробуйте выполнить то, что вы делаете, с помощью одной кнопки/списка/потока. Затем, как только вы заработаете, развивайте это. Что-то вроде этой статьи joplaete.wordpress.com/2010/07/ 21/threading-with-pyqt4   -  person jross    schedule 01.04.2013


Ответы (4)


Приложение с графическим интерфейсом идеально подходит для тестирования, так как легко создавать новые задачи и визуализировать происходящее, поэтому я написал небольшой пример приложения (Скриншот, код ниже), так как я хотел изучить его для себя.

Сначала я использовал тот же подход, что и ваш, пытаясь реализовать шаблон «Потребитель / Производитель», и я боролся с фоновыми процессами, выполняя бесконечные циклы, чтобы ждать новых заданий, и позаботился о обмене данными для себя. Затем я узнал об интерфейсе пула, а затем можно заменить весь этот отвратительный код всего несколькими строками. Все, что вам нужно, это один пул и несколько обратных вызовов:

#!/usr/bin/env python3
import multiprocessing, time, random, sys
from PySide.QtCore import * # equivalent: from PyQt4.QtCore import *
from PySide.QtGui import *   # equivalent: from PyQt4.QtGui import *

def compute(num):
    print("worker() started at %d" % num)
    random_number = random.randint(1, 6)
    if random_number in (2, 4, 6):
        raise Exception('Random Exception in _%d' % num)
    time.sleep(random_number)
    return num

class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.toolBar = self.addToolBar("Toolbar")
        self.toolBar.addAction(QAction('Add Task', self, triggered=self.addTask))
        self.list = QListWidget()
        self.setCentralWidget(self.list)

        # Pool of Background Processes
        self.pool = multiprocessing.Pool(processes=4)

    def addTask(self):
        num_row = self.list.count()
        self.pool.apply_async(func=compute, args=(num_row,), callback=self.receiveResult,
                              error_callback=self.receiveException)
        item = QListWidgetItem("item %d" % num_row)
        item.setForeground(Qt.gray)
        self.list.addItem(item)

    def receiveResult(self, result):
        assert isinstance(result, int)
        print("end_work(), where result is %s" % result)
        self.list.item(result).setForeground(Qt.darkGreen)

    def receiveException(self, exception):
        error = str(exception)
        _pos = error.find('_') + 1
        num_row = int(error[_pos:])
        item = self.list.item(num_row)
        item.setForeground(Qt.darkRed)
        item.setText(item.text() + ' Retry...')
        self.pool.apply_async(func=compute, args=(num_row,), callback=self.receiveResult,
                              error_callback=self.receiveException)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

Изменить: я сделал еще один пример, используя QTimer вместо обратных вызовов, периодически проверяя записи в очереди, обновляя QProgressBar:

#!/usr/bin/env python3
import multiprocessing, multiprocessing.pool, time, random, sys
from PySide.QtCore import *
from PySide.QtGui import *

def compute(num_row):
    print("worker started at %d" % num_row)
    random_number = random.randint(1, 10)
    for second in range(random_number):
        progress = float(second) / float(random_number) * 100
        compute.queue.put((num_row, progress,))
        time.sleep(1)
    compute.queue.put((num_row, 100))

def pool_init(queue):
    # see http://stackoverflow.com/a/3843313/852994
    compute.queue = queue

class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.toolBar = self.addToolBar("Toolbar")
        self.toolBar.addAction(QAction('Add Task', self, triggered=self.addTask))
        self.table = QTableWidget()
        self.table.verticalHeader().hide()
        self.table.setColumnCount(2)
        self.setCentralWidget(self.table)

        # Pool of Background Processes
        self.queue = multiprocessing.Queue()
        self.pool = multiprocessing.Pool(processes=4, initializer=pool_init, initargs=(self.queue,))

        # Check for progress periodically
        self.timer = QTimer()
        self.timer.timeout.connect(self.updateProgress)
        self.timer.start(2000)

    def addTask(self):
        num_row = self.table.rowCount()
        self.pool.apply_async(func=compute, args=(num_row,))
        label = QLabel("Queued")
        bar = QProgressBar()
        bar.setValue(0)
        self.table.setRowCount(num_row + 1)
        self.table.setCellWidget(num_row, 0, label)
        self.table.setCellWidget(num_row, 1, bar)

    def updateProgress(self):
        if self.queue.empty(): return
        num_row, progress = self.queue.get() # unpack
        print("received progress of %s at %s" % (progress, num_row))
        label = self.table.cellWidget(num_row, 0)
        bar = self.table.cellWidget(num_row, 1)
        bar.setValue(progress)
        if progress == 100:
            label.setText('Finished')
        elif label.text() == 'Queued':
            label.setText('Downloading')
        self.updateProgress() # recursion

if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())
person valmynd    schedule 08.04.2013
comment
Спасибо @valmynd, я уверен, что это чрезвычайно полезно для многих в сообществе. Однако я заметил, что функция apply_async здесь использует возвращаемое значение метода, заданного для callback. Другими словами, вызывается callback=self.recieveResult и передается результат compute в качестве параметра. Как бы вы сделали эту настройку возможной для получения данных из процесса в любой точке цикла или внутри функции, которую вызывает compute, и т. д.? - person chase; 08.04.2013
comment
@chase Самый простой способ использовать такие обратные вызовы, как я нашел, - это передать функцию в качестве аргумента вашему compute. Таким образом, он может вызывать recieveResult так часто, как захочет. Единственная проблема заключается в том, что это не потокобезопасно и может создать проблему, если ваша функция-обработчик делает что-то сложное. Способ обойти это, конечно, состоит в том, чтобы создать очередь результатов, в которую ваш compute fxn записывает, а ваша функция-обработчик читает. Если обработчик находится в своем собственном потоке, я чувствую, что это сработает. - person DaveTheScientist; 09.04.2013
comment
@chase Я отредактировал свой ответ и добавил еще один пример без обратных вызовов, который отвечает вашим требованиям и может считаться более безопасным. В первом примере был серьезный недостаток: обновление QProgressbar из метода обратного вызова было невозможно, так как казалось, что он выполняется вне потока графического интерфейса (здесь у кого-то были похожие проблемы). - person valmynd; 09.04.2013
comment
Спасибо @valmynd, похоже, вы реализовали то, что @DaveTheScientist описал в своем предыдущем комментарии. Мне нравится это решение, потому что похоже, что оно действительно обновляет индикатор выполнения графического интерфейса! Тем не менее, это может быть только моя настройка здесь, но взаимодействие с графическим интерфейсом, похоже, не работает со второй заданной настройкой. Столбцы переключаются с серого на синий при нажатии перед запуском функции addTask, в то время как столбцы перестают быть интерактивными после нажатия кнопки «Добавить задачу». Это потому, что self.timer подключен к self.updateProgess , который находится в том же потоке? - person chase; 10.04.2013
comment
@chase извините, я не могу воспроизвести это поведение на своей машине, я пробовал это с Python 3.2 в Ubuntu 12.04 и с Python 3.3 в Windows 8 (однако не пробовал с PyQt вместо PySide). Даже вызов QTimer.start() с интервалом 10 вместо 2000 миллисекунд работал хорошо. Было ли это бездействие только в течение очень коротких моментов? Думаю, что размещение updateProgess() в другом потоке не поможет из-за GIL. Я останусь на этом, может быть, у меня будет возможность протестировать это на нетбуке. - person valmynd; 10.04.2013
comment
@valmynd Я попробовал это сегодня на другом компьютере, и графический интерфейс хорошо обновляется. Большое тебе спасибо! Сейчас я пытаюсь понять, что происходит, достаточно хорошо, чтобы переместить это в мою основную программу. - person chase; 10.04.2013
comment
Да, спасибо, что указали на это, я столкнулся с той же проблемой и решил придерживаться простых функций, игнорируя проблему. Приятно видеть, что это возможно возможно ;) - person valmynd; 12.04.2013

Большое спасибо за размещение этого вопроса, и всем участникам за их вклад. Это дало мне полезную основу для экспериментов с PyQt и многопроцессорностью.

Я начал со второго примера кода, указанного в вопросе. Мои изменения и комментарии:

  • В Windows все аргументы Process.__init__() должны быть перебираемыми. Вы увидите, что @valmynd делает свою функцию compute функцией верхнего уровня именно по этой причине. Отчасти это связано с тем, что многопроцессорная обработка повторно импортирует модуль, содержащий целевую функцию. Чтобы напомнить себе об этом, я пытаюсь поместить целевую функцию в отдельный модуль (и убедиться, что любая информация передается в виде доступных для выбора аргументов). Я переместил сложную функцию обработки в отдельный модуль под названием workermodule.py.
  • Без достаточной работы со сложной функцией обработки цикл завершается слишком быстро, чтобы любые изменения были видны в графическом интерфейсе. Поэтому я добавил лишнюю (бесполезную) работу внутрь сложной функции обработки. Как было отмечено в комментарии, можно было бы time.sleep, но приятнее немного засветить все ядра.
  • С помощью следующих двух фрагментов кода я получаю плавный графический интерфейс с непрерывными обновлениями значения итерации, а подпроцессы выполняются на полной скорости.
  • Обратите внимание, что self.process можно создать с двумя очередями в качестве аргументов, одной для ввода и одной для вывода. Тогда сложная функция обработки должна будет периодически проверять входную очередь на наличие данных.

рабочий модуль.py:

import time

def some_complex_processing(queue):
    iteration = 0
    for i in range(0,5000):
        iteration += 1
        queue.put(iteration)
        #time.sleep(20e-3) # ms
        # You could time.sleep here to simulate a
        # long-running process, but just to prove
        # that we're not cheating, let's make this
        # process work hard, while the GUI process
        # should still have smooth drawing.
        for x in range(100000):
            y = x
    queue.put('done with processing')

основной файл.py:

from PyQt4 import QtCore, QtGui
import multiprocessing as mp
import sys
import workermodule

class Worker(QtCore.QObject):
    update_signal = QtCore.pyqtSignal(int)
    done_signal = QtCore.pyqtSignal()

    def __init__(self, some_var):
        QtCore.QObject.__init__(self, parent=None)
        self.some_var = some_var
        self.queue = mp.Queue()
        self.process = mp.Process(
            target=workermodule.some_complex_processing,
            args=(self.queue,)
            )

    @QtCore.pyqtSlot()
    def start_computation(self):
        self.process.start()
        while True:
            try:
                message = self.queue.get()
                self.update_signal.emit(message)
            except EOFError:
                pass
            if message == 'done with processing':
                self.done_signal.emit()
                break
        self.process.join()
        return

class Tab(QtGui.QTabWidget):
    start_comp = QtCore.pyqtSignal()
    def __init__(self, parent, this_worker):
        self.parent = parent
        self.this_worker = this_worker
        QtGui.QTabWidget.__init__(self, parent)

        self.treeWidget = QtGui.QTreeWidget(self)
        self.properties = QtGui.QTreeWidgetItem(self.treeWidget, ["Properties"])
        self.step = QtGui.QTreeWidgetItem(self.properties, ["Iteration #"])

        # Use QThread is enough
        self.thread = QtCore.QThread();
        # Change the thread affinity of worker to self.thread.
        self.this_worker.moveToThread(self.thread);
        self.this_worker.update_signal.connect(self.update_GUI)
        self.this_worker.done_signal.connect(self.thread.quit)
        self.start_comp.connect(self.this_worker.start_computation)
        self.thread.start()

    ###############################
    # Here is what should update the GUI at every iteration of Worker.some_complex_processing()
    # The message appears to be getting sent, due to seeing the print statement in the console, but the GUI is not updated.
    @QtCore.pyqtSlot(int)
    def update_GUI(self, iteration):
        self.step.setText(0, str(iteration))
        print iteration

    def start_signal_emit(self):
        self.start_comp.emit()

# GUI stuff
class MainWindow(QtGui.QMainWindow):
    def __init__(self, parent = None):
        QtGui.QMainWindow.__init__(self)
        self.tab_list = []
        self.setTabShape(QtGui.QTabWidget.Rounded)
        self.centralwidget = QtGui.QWidget(self)
        self.top_level_layout = QtGui.QGridLayout(self.centralwidget)

        self.tabWidget = QtGui.QTabWidget(self.centralwidget)
        self.top_level_layout.addWidget(self.tabWidget, 1, 0, 25, 25)

        process_button = QtGui.QPushButton("Process")
        self.top_level_layout.addWidget(process_button, 0, 1)
        QtCore.QObject.connect(process_button, QtCore.SIGNAL("clicked()"), self.process)

        self.setCentralWidget(self.centralwidget)
        self.centralwidget.setLayout(self.top_level_layout)

        # Make Tabs in loop from button
        for i in range(0,10):
            name = 'tab' + str(i)
            self.tab_list.append(Tab(self.tabWidget, Worker(name)))
            self.tabWidget.addTab(self.tab_list[-1], name)

    # Do the processing
    def process(self):
        for tab in self.tab_list:
            tab.start_signal_emit()
        return

if __name__ == "__main__":
    app = QtGui.QApplication([])
    win = MainWindow()
    win.show()
    sys.exit(app.exec_())
person Caleb Hattingh    schedule 10.10.2013
comment
Вы дважды заменили функцию получения свойством, а именно: self.parent = parent, self.centralWidget = QtGui.QWidget(self). Это не очень хорошая практика. - person MadeOfAir; 11.10.2013

Все, что вы сделали в Worker_Thread, должно было быть перенесено в Worker. Qt присваивает привязку потока каждому из объектов в зависимости от того, где объект создан. Объекты Worker_Thread создаются в основном потоке, поэтому они имеют сходство с основным потоком. Если сигнал из основного потока подключен к слоту объекта, созданного в основном потоке, слот также будет выполняться в основном потоке. (Независимо от того, QueuedConnection или DirectConnection). И слот блокирует графический интерфейс.

Сделай это:

class Worker:
    update_signal = QtCore.pyqtSignal(int)
    done_signal = QtCore.pyqtSignal()

    def __init__(self, some_var):
        self.some_var = some_var
        self.iteration = 0
        self.queue = mp.Queue()
        self.process = mp.Process(target=self.some_complex_processing, args=(self.queue,))

    def some_complex_processing(self, queue):
        for i in range(0,5000):
            self.iteration += 1
            queue.put(self.iteration)
        queue.put('done with processing')

    @QtCore.pyqtSlot()
    def start_computation(self):
        self.process.start()
        while(True):
            try:
                message = self.queue.get()
                self.update_signal.emit(message)
            except EOFError:
                pass
            if message == 'done with processing':
                self.done_signal.emit()
                break
        self.process.join()
        return



class Tab(QtGui.QTabWidget):
    start_comp = QtCore.pyqtSignal()
    def __init__(self, parent, this_worker):
        self.parent = parent
        self.this_worker = this_worker
        QtGui.QTabWidget.__init__(self, parent)

        self.treeWidget = QtGui.QTreeWidget(self)
        self.properties = QtGui.QTreeWidgetItem(self.treeWidget, ["Properties"])
        self.step = QtGui.QTreeWidgetItem(self.properties, ["Iteration #"])

        # Use QThread is enough
        self.thread = QtCore.QThread();
        # Change the thread affinity of worker to self.thread.
        self.this_worker.moveToThread(self.thread);
        self.this_worker.update_signal.connect(self.update_GUI)
        self.this_worker.done_signal.connect(self.thread.quit)
        self.start_comp.connect(self.this_worker.start_computation)
        self.thread.start()

    ###############################
    # Here is what should update the GUI at every iteration of Worker.some_complex_processing()
    # The message appears to be getting sent, due to seeing the print statement in the console, but the GUI is not updated.
    @QtCore.pyqtSlot(int)
    def update_GUI(self, iteration):
        self.step.setText(0, str(iteration))
        #time.sleep(0.1)
        print iteration

    def start_signal_emit(self):
        self.start_comp.emit()
person Min Lin    schedule 06.04.2013
comment
Огромное спасибо за помощь. Это, безусловно, коснулось какой-то сути проблемы, но теперь я по какой-то причине не могу вывести сообщения из процесса обновления графического интерфейса. Я загружу редактирование в свой ответ и продолжу работу над ним, чтобы узнать, есть ли у вас или у меня какие-либо другие идеи. - person chase; 07.04.2013
comment
у вас есть идеи, почему текстовые поля не устанавливаются в графическом интерфейсе с помощью этого кода? - person chase; 11.04.2013

Хорошо, я не знаком с самим Qt, но я проделал аналогичную работу с Tkinter. Я почти уверен, что здесь вы сталкиваетесь с глобальной блокировкой интерпретатора Python. В частности, вы запускаете свою очередь и приложение с графическим интерфейсом в одном и том же потоке, поэтому, когда очередь блокируется в ожидании ввода, блокируется и графический интерфейс. Попробуйте запустить app = QtGui.QApplication([]) в отдельном потоке или процессе. Написание графического интерфейса с очередями всегда довольно сложно, и я обнаружил, что обычно требуется как минимум еще один уровень потоковой обработки, чем я ожидал.

person DaveTheScientist    schedule 05.04.2013
comment
Спасибо @DaveTheScientist, я, конечно, слышал это объяснение GIL для своих проблем раньше, но я не совсем понимаю, как я все еще создаю очередь в том же потоке, что и графический интерфейс, когда я вызываю ее в отдельном классе Worker_Thread. Разве он не должен передавать все сообщения из процесса в поток, который создается рядом с вкладкой, а затем передавать эти сообщения в основной поток графического интерфейса в моей настройке? По крайней мере, это была моя цель для настройки, которую я дал. Если нет, то запускаю ли я графический интерфейс в отдельном потоке, если это не так? - person chase; 06.04.2013
comment
Хорошо, @chase, кажется, я вижу, как ты все это устроил. Теперь мой вопрос: в вашем MainWindow class, когда вернется tab.start_signal_emit()? Возможно ли, что он блокируется до тех пор, пока каждая вкладка не будет завершена? Если это так, это будет запускать процессы вкладок, и они будут вызывать self.step.setText(0, str(iteration)) и print, как и ожидалось, но эти вызовы setText будут добавлены во внутреннюю очередь вашего графического интерфейса и фактически не будут выполняться до тех пор, пока все вызовы tab.start_signal_emit() не вернутся. . - person DaveTheScientist; 06.04.2013
comment
Если это проблема, я бы рекомендовал начинать новый поток (или процесс из mp) для каждой итерации в этом цикле for в вашем MainWindow. Еще одна вещь, которую можно попробовать, может заключаться в том, что в Worker_Thread.start_computation(), когда он вызывает self.queue.get(), добавьте значение тайм-аута: self.queue.get(True, 1.0). - person DaveTheScientist; 06.04.2013
comment
Я создал поток для каждого процесса в моем цикле for, используя этот код: # Do the processing def process(self): for tab in self.tab_list: thread = QtCore.QThread(self) tab.moveToThread(thread) tab.start_signal_emit() thread.start() return, но у меня все еще те же проблемы, что и раньше. Я также пробовал значение тайм-аута, но безуспешно. Я думаю, что ответ Мин Лин имеет какое-то отношение к тому, что происходит, и кажется, что близость к треду также мотивирует ваши идеи для ответов, но это еще не сработало. - person chase; 07.04.2013