我有一个用
Python3
编写的文件管理器应用程序,使用 PyQt5
在 Linux
下运行。我希望能够执行多个并发文件操作,例如复制和移动。
我尝试了几种方法:Python ThreadPoolExecutor 和 ProcessPoolExecutor; PyQt QThread.
我能够使用所有这些方法执行多个并发操作,但是当线程或进程需要为用户输入显示 QDialog 时,所有操作都会失败。显示一个空窗口,线程或进程被阻塞。这些对话框用于诸如副本即将替换现有文件并要求用户确认或报告复制大文件的进度等情况。
这种情况不同于主 GUI 报告工作人员进度的更典型情况。在这种情况下,主 GUI 程序不知道工作人员在做什么以及他们需要显示什么对话框。我知道线程之间(或进程之间的管道)之间的信号和槽是可能的,但这在这里似乎不实用。对话的需要和内容由工作人员决定,主线程/进程不知道这一点。
Qt 事件循环不需要在主 GUI 进程中显示和处理 QDialog 小部件,但我还没有找到任何方法来显示工作对话框,除非事件循环正在该工作人员中运行。我发现这样做的唯一方法是在 QThread 实例中。当我这样做时,一个线程中的事件循环似乎阻塞了所有线程,这违背了这个项目的全部目的。
有什么方法可以在非主 GUI 线程或进程的线程或进程中创建和显示 PyQt QDialog 小部件,并且不会阻塞所有其他线程?
不要在工作线程中操作 GUI。为什么?总之,因为Qt不支持这种场景。大多数其他 GUI 框架也是如此。 (Musicamante 已经在他的评论中对此进行了很好的详细解释。)
因此,在工作线程中调用
Signal.emit()
或 QCoreApplication.postEvent()
来通知主线程,以便它操作 GUI。有关详细信息,请参见线程和 QObjects。
下面是一个集成Python标准线程API和Qt事件循环的例子
import sys
import time
import threading
from PyQt5.QtCore import QObject, QEvent, QCoreApplication
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox
class CallHandler(QObject):
def event(self, e):
e.func()
return True
CALL_EVENT_TYPE = QEvent.registerEventType()
_call_handler = CallHandler()
# This schedules func to be called in the main thread and is thread-safe.
def post_call(func):
e = QEvent(CALL_EVENT_TYPE)
e.func = func
QCoreApplication.postEvent(_call_handler, e)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__(windowTitle=sys.argv[0])
self.resize(400, 300)
def start_thread(i):
dlg = QMessageBox(QMessageBox.Question,
sys.argv[0], f'confirm#{i}',
QMessageBox.Ok | QMessageBox.Cancel,
self, modal=False)
dlg_closed = False
cv = threading.Condition()
def on_dlg_finished(_):
nonlocal dlg_closed
with cv:
dlg_closed = True
cv.notify()
dlg.finished.connect(on_dlg_finished)
def entry():
post_call(dlg.show)
time.sleep(2)
with cv:
if not dlg_closed:
cv.wait()
print('entry, i:', i, ', res:', dlg.result())
post_call(on_entry_finished)
def on_entry_finished():
# This will be run in the main thread
dlg.destroy()
thread.join()
thread = threading.Thread(target=entry)
thread.start()
for i in range(2):
start_thread(i)
app = QApplication([])
main_window = MainWindow()
main_window.show()
app.exec_()
作为旁注,如果您对我关于这项技术的个人历史感兴趣,请参阅这个问题。