#python #multiprocessing #pyside2
Вопрос:
У меня есть многопроцессорное графическое приложение, которое безупречно работает при вызове с помощью python на Mac.
Для запуска моих процессов и func
асинхронного запуска функции я использую multiprocessing.pools
:
def worker(self):
calulation_promise = self._pool.map_async(func, (self.simulated_values),
callback=self.simFin, error_callback=self.simError)
return calulation_promise
Теперь мне нужно создать исполняемый файл из моего проекта с помощью cx-freeze. Я использую шаблон документации, предоставленный здесь.
import sys
from cx_Freeze import setup, Executable
# Dependencies are automatically detected, but it might need fine tuning.
# "packages": ["os"] is used as example only
excludes = ["Pyside2.Qt5WebEngineCore.dll", "PySide6"]
build_exe_options = {"packages": ['multiprocessing'], "excludes": excludes}
# base="Win32GUI" should be used only for Windows GUI app
base = None
if sys.platform == "win32":
base = "Win32GUI"
setup(
name = "guifoo",
version = "0.1",
description = "My GUI application!",
options = {"build_exe": build_exe_options},
executables = [Executable("main.py", base=base)]
)
К сожалению, когда мое основное приложение вызывает мою worker ()
функцию, оно всегда запускает основной процесс (который запускает новый графический интерфейс главного окна).
Это означает, что вместо выполнения функции func
она каким-то образом запускает основной поток снова и снова (для большей ясности см. Выходные данные).
Рабочий пример:
Примечание: Это рабочий пример для воспроизведения проблемы.
import sys, os, time, logging, platform, multiprocessing, random
from multiprocessing import Process, Pool, cpu_count, freeze_support
from PySide2.QtWidgets import (QLineEdit, QPushButton, QApplication, QVBoxLayout, QDialog)
from PySide2.QtCore import Signal, QObject
from rich.logging import RichHandler
class Form(QDialog):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
logging.info ("Stared gui...")
# Create widgets
self.edit = QLineEdit("<empty>")
self.button = QPushButton("Start worker")
# Create layout and add widgets
layout = QVBoxLayout()
layout.addWidget(self.edit)
layout.addWidget(self.button)
self.setLayout(layout) # Set dialog layout
self.button.clicked.connect(self.startWorker) # Add button signal to greetings slot
# Greets the user
def startWorker(self):
logging.info("Stared worker...")
tw = ThreadWrapper()
self.promise = tw.worker()
tw.signals.finished.connect(self.finished)
def finished(self):
self.edit.setText(str(self.promise.get()))
class ThreadWrapper():
def __init__(self):
self.simulated_values = range(1, 30, 1)
self._pool = Pool(processes=8)
self.signals = WorkerSignals()
def simFin(self, value):
logging.info("%s" % (value))
self.signals.finished.emit()
def simError(self, value):
logging.error("%s" % (value))
def worker(self):
calulation_promise = self._pool.map_async(func, (self.simulated_values),
callback=self.simFin, error_callback=self.simError)
return calulation_promise
class WorkerSignals(QObject):
finished = Signal()
# A function which needs an arbitrary amount of time to finish
def func(value):
wait = random.randint(1, 5); time.sleep(wait)
res = value**value
print("THREAD: %d*%d = %d; waiting %d" % (value, value, res, wait))
return res
def main():
logging.basicConfig(level="DEBUG", format="%(name)s | %(message)s", datefmt="[%X]", handlers=[RichHandler()])
if platform.system() == "Darwin":
multiprocessing.set_start_method('spawn')
os.environ['QT_MAC_WANTS_LAYER'] = '1'
os.environ['QT_MAC_USE_NSWINDOW'] = '1'
app = QApplication(sys.argv)
window = Form()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Выходы
Использование python
[20:41:50] INFO root | Stared gui... _main.py:11
[20:41:52] INFO root | Stared worker... _main.py:24
THREAD: 3*3 = 27; waiting 1
THREAD: 4*4 = 256; waiting 3
THREAD: 1*1 = 1; waiting 5
THREAD: 2*2 = 4; waiting 5
[20:41:57] INFO root | [1, 4, 27, 256]
Использование исполняемого файла
[20:44:03] INFO root | Stared gui... _main.py:11
[20:44:05] INFO root | Stared worker... _main.py:24
[20:44:06] INFO root | Stared gui... _main.py:11
[20:44:06] INFO root | Stared gui... _main.py:11
[20:44:06] INFO root | Stared gui... _main.py:11
[20:44:06] INFO root | Stared gui... _main.py:11
[20:44:06] INFO root | Stared gui... _main.py:11
[20:44:06] INFO root | Stared gui... _main.py:11
[20:44:06] INFO root | Stared gui... _main.py:11
[20:44:06] INFO root | Stared gui... _main.py:11
[20:44:06] INFO root | Stared gui... _main.py:11
Дополнительные сведения:
- Mac OS Big Sur, версия 11.5.1
- Python 3.7.4
- PySide2, версия 5.15.2
Комментарии:
1. Тебе, наверное, стоит позвонить
freeze_support
main
. Вы импортируете его, но на самом деле никогда не называли его.2. Согласно официальной документации , он действителен только для исполняемых файлов Windows: вызов функции freeze_support() не имеет эффекта при вызове в любой операционной системе, кроме Windows. Поэтому я его не включил.
3. Я понимаю, что это так, но поведение, которое вы описываете, аналогично тому, что
freeze_support
вы пытаетесь решить. Я не знаком с cx-замораживанием на macOS, так что это может быть не решением, но попробовать стоит, нет? Python почти недавно начал по умолчанию «появляться» в macOS, что больше похоже на то, как всегда должна работать Windows.4. Да, я понимаю вашу точку зрения, и на самом деле я попробовал это в своем приложении, где эта проблема возникла в первую очередь, но это ничего не изменило.
5. хотя… он уже должен использовать «вилку», потому что «spawn» был обновлением 3.8…. ИДК стоит попробовать
mp.set_start_method('fork')