Как реализовать многопоточность в архитектурном шаблоне MVP?

Я программирую настольное приложение для небольшого местного отеля, используя Python.
Как это часто бывает, в фоновом режиме у меня также есть сложный процесс/метод. В это время индикатор выполнения должен появиться и заполниться. Индикатор выполнения должен блокировать дальнейший ввод и служить информацией для пользователя: «Данные/задачи обрабатываются».

Моя проблема:
Я не могу добиться и того, и другого: запустить heavy_load_method в фоновом режиме и одновременно показать и заполнить панель прогресса.

Я предполагаю, что я неправильно реализую свой индикатор выполнения для многопоточности/многопроцессорности.

Поскольку моя неудача, возможно, связана с моей текущей реализацией MVP, некоторые фрагменты кода:

# main.py
# importing all modules (presenter, model and all views/formulars (including the progressbar view/form)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    mainwindow = Mainwindow()
    progressbar = ProgressBarPopup()
    model = Model()
    ...
    presenter = Presenter(model, mainwindow, progressbar, ...)
    sys.exit(app.exec_())
# presenter.py
# imports modules required for data manipulation

class Presenter:
    def __init__(self, model, mainwindow, progressbar, ...)
        self.model = model
        self.mainwindow = mainwindow
        self.progressbar = progressbar
        ...

    def heavy_load_method(self):
        # does operations like:
        #  - getting (minor) amounts of data from form/view (related view/form method calls)
        #  - processes already cached `self.data` (via Presenter method calls)
        #  - calling and calculating data from the model/database
        #  - calling functions from imported moduls like "myfilemanagement.py"
        #  - ...
# MyProgressbar.py
from PyQt5.QtWidgets import QProgressBar, QDialog, QVBoxLayout, QLabel
from PyQt5.QtCore import Qt, QTimer

class ProgressBarPopup(QDialog):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Progressbar")
        self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint)
        self.setFixedSize(300, 100)
        self.setWindowModality(Qt.ApplicationModal)
        layout = QVBoxLayout()
        self.label = QLabel("Processing...", self)
        self.label.setAlignment(Qt.AlignCenter)
        layout.addWidget(self.label)
        self.progressbar = QProgressBar(self)
        self.progressbar.setRange(0, 99)
        layout.addWidget(self.progressbar)
        self.setLayout(layout)

        # Progressbar visual update timer
        self.step = 0
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update_progress)

        # Progressbar exit/close timer
        self.close_timer = QTimer(self)
        self.close_timer.timeout.connect(self.close_progressbar)


    def close_progressbar(self):
        self.close_timer.stop()
        self.close()

    def update_progress(self):
        self.step += 1
        if self.step <= 98:
            self.progressbar.setValue(self.step)
        if self.step > 98:
            self.timer.stop()
            self.label.setText("This takes longer then expected.")


    def setup_progressbar_and_show(self):
        self.step = 0
        self.progressbar.setValue(self.step)
        self.timer.start(70)
        self.close_timer.start(10000)
        self.label.setText("Processing...") 
        self.show()

Я попробовал multithread или multiprocess метод heavy_load_method. Мои предыдущие попытки не увенчались успехом, потому что я не смог multithread/multiprocess индикатор выполнения.

У вас есть причина не использовать QProgressDialog? Также смотрите stackoverflow.com/a/63940295/51685

AKX 10.04.2024 12:16

@AKX, я полагаю, нет? Я буду смотреть в него. Я подозревал, что неправильно реализовал индикатор выполнения (индикатор выполнения передается ведущему, поэтому он, вероятно, не становится внезапно многопоточным во время выполнения).

Andreas M. 10.04.2024 12:26

@AKX Судя по вашей ссылке: мне нужно создать еще один класс, унаследованный от QThread, который получит почти полную копию Presenter? (heavy_duty_method требуется доступ к модели/базе данных и т. д.)

Andreas M. 10.04.2024 13:39

Шаблоны MVC/MVP часто не нужны при использовании Qt, но если «глобальный менеджер» (контроллер/презентатор) действительно требуется структурой программы, обычно лучше использовать его как подкласс QObject (позволяющий правильному сигналу/слоту). функции), и это часто достигается за счет использования самого QApplication, поскольку обычно это общий объект и всегда существует только один его экземпляр. Любую «тяжелую» обработку затем следует делегировать отдельному объекту (обычно QThread), который никогда не будет напрямую обращаться к пользовательскому интерфейсу, а будет работать только с данными и в конечном итоге сигнализировать о результатах.

musicamante 10.04.2024 16:37

@musicamante Вы предполагаете, что мой класс Presenter должен наследовать от QApplication? Я создал класс Worker, очень похожий на stackoverflow.com/a/63940295/51685. (Скопированы необходимые модули и методы, инициализированы (конструктор) необходимыми данными. run(self) вызывает «heavy_duty_method»). Вылетает self.worker.started(self.progressDialog.show()) => TypeError: native Qt signal is not callable. В настоящее время я читаю QThread.started. Что может вызвать эту ошибку?

Andreas M. 10.04.2024 16:59

Ошибка очевидна: сигналы нельзя вызвать, вам нужно подключиться к сигналу, а не вызвать его. Кроме того, в соединениях сигналов вы передаете ссылку на функцию, а не вызываете ее. Перейдите на self.worker.started.connect(self.progressDialog.show), затем узнайте больше о сигналах и слотах, например, начиная с этого.

musicamante 10.04.2024 18:30

@musicamante Если шаблоны MVC/MVP не нужны для (в моем случае) настольного инструмента бронирования: какие шаблоны обычно применяются? Я выбрал MVC/MVP, потому что мне нужна была структура, которой можно было бы следовать, и которую было бы легко расширить.

Andreas M. 10.04.2024 20:46

@АндреасМ. Шаблоны программирования — это общие концептуальные структуры: от нас не требуется следовать ни одной из них, и выбор одной из них не означает, что мы должны строго следовать ее «известной» структуре. Это абстракции, концепции упрощения и, по сути, просто рекомендации. Хотя это правда, что MVC/MVP легче следовать и расширять, верно также и то, что слепое следование таким шаблонам «потому что так следует делать» — ужасный выбор. Шаблоны следует применять тогда, когда они имеют смысл, и пока их внедрение и поддержка выгодны, что не соответствует общим правилам.

musicamante 11.04.2024 04:01

@АндреасМ. Например, известное руководство по калькулятору Realpython.com является прекрасным примером: оно ясно показывает, как MVC может быть достигнуто с помощью PyQt, но также забывает объяснить, почему такой шаблон совершенно бессмысленен для этого базового примера; Интересно, что многие вопросы здесь, по SO, были закрыты из-за этого поста, потому что реальная цель поста была неправильно понята как общее правило, которому нужно следовать, а не как руководство, которое следовало понять, прежде чем его применять с самого начала.

musicamante 11.04.2024 04:01

@musicamante Кажется, я понял MVC и MVP. Однако примеры не были написаны с использованием pyqt5. У меня также был опыт работы с многопоточностью (или даже с многопроцессорностью), даже если это было много лет назад. На тот момент не было необходимости (по крайней мере, я этого не помню) выделять в отдельный класс(ы). Вам просто нужно было убедиться, что расчеты на самом деле на 100% независимы друг от друга. Кроме того, нагрузка/вычисления были разделены таким образом, чтобы каждая отдельная часть требовала примерно одинакового вычислительного времени. Поскольку такое разделение (поток/процесс) было невозможно с помощью pyqt5, был создан этот пост.

Andreas M. 11.04.2024 11:07

@musicamante Мои навыки программирования получены в основном из «высшего профессионального колледжа информатики» и изучения математики (немного Java, в основном Python). В идеале я бы сходил на лекцию или прочитал книгу, чтобы восполнить недостаток современных навыков программирования. К сожалению, полной работы по этому поводу я не нашел. Поэтому я благодарен за любую помощь. Особенно если он очень компетентный и явно выше моего уровня. Я прочитаю обе ваши ссылки полностью (даже если предполагаю, что уже знаю, НО: вижу, что не знал). Прошу прощения за качество текущего вопроса.

Andreas M. 11.04.2024 12:44
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
11
80
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Лучше создать пакетную функцию, обрабатывающую небольшой объем данных. Вы можете вызывать эти функции, создавая поток для каждой из них, управляя их количеством с помощью очереди (для запуска потоков rn) и списка (например, для запуска потоков, когда у вас будет больше памяти). Вы будете знать, когда каждая из функций была остановлена ​​(завершена) из основного потока пользовательского интерфейса, и для каждой завершенной пакетной функции вам нужно добавить процент уже обработанных данных в строку состояния.

Проверьте функцию nn_labeling здесь (потоковая обработка): https://github.com/ISPlatonov/Dataset-generation/blob/python/backend/HandSegmentor.py

Индикатор выполнения: https://github.com/ISPlatonov/Dataset-generation/blob/python/frontend/step%202/threshold.qml

Этот проект основан на PySide6 (PyQt с другой лицензией).

Меньший объем данных — это заголовок счета, а также некоторые другие настройки, относящиеся к счету. Его можно настроить, поэтому я загружаю его через self.invoice_view.get_Invoice_Header()` в файл heavy_load_method. Я не думаю, что это является причиной моей проблемы. Возможно, мне следовало сформулировать вопрос точнее (я думал, что это будет достаточно точно).

Andreas M. 10.04.2024 12:35

Кажется, моя проблема в том, что мой индикатор выполнения не является многопоточным.

Andreas M. 10.04.2024 12:37

Это не обязательно, так как вы можете установить значение индикатора выполнения на значение qt, которое будет обновляться при отправке сигнала.

Ilush 10.04.2024 12:41
Ответ принят как подходящий

Мне немного стыдно, что это заняло так много времени, но:

Даже если вы интуитивно полагаете, что вам нужно использовать QThread для индикатора выполнения (поскольку это объект pyqt5), на самом деле все происходит наоборот. heavy_duty_method необходимо отделить от презентатора и поместить в отдельный подкласс QThread. На основе этого во время выполнения создается объект, который можно интегрировать и запустить, как показано ниже.

# presenter.py
# imports modules required for data manipulation and 'Create_Heavy_Duty_Thread' class

class Presenter:
    def __init__(self, model, mainwindow, progressbar, ...)
        self.model = model
        self.mainwindow = mainwindow
        self.progressbar = progressbar
        ...

    def prepare_for_load(self)
        if conditions_are_met:
            self.worker = Create_Heavy_Duty_Thread(self.model, invoice_dict, bookings, isClassic)
            self.worker.started.connect(self.progressbar.setup_progressbar_and_show)
            self.worker.finished.connect(self.progressbar.close_progressbar)
            self.worker.start()
class Create_Heavy_Duty_Thread(QThread):
    def __init__(self, model, invoice_dict, bookings, isClassic):
        super().__init__()
        self.model = model
        self.invoice_dict = invoice_dict
        self.bookings = bookings
        self.isClassic = isClassic

    ... # all methods needed to perform heavy_duty_method

    def heavy_duty_method(self):
        # heavy duty is done here 
        ...

    def run(self):
        self.heavy_duty_method()

Другие вопросы по теме