PyQt对话框能否在多线程/多进程应用的非主线程/进程独立显示且不阻塞?
我基于Python3、PyQt5开发了一款Linux下的文件管理器应用,想要实现复制、移动这类多并发文件操作。
我试过几种方案:Python的ThreadPoolExecutor、ProcessPoolExecutor,还有PyQt的QThread。这些方案都能实现多并发,但当线程/进程需要弹出QDialog获取用户输入时都会失败——窗口是空的,线程/进程也会被阻塞。这些对话框的使用场景包括:复制即将覆盖现有文件时请求用户确认,或者显示大文件复制的进度条。
这种情况和常见的「主GUI展示工作进程进度」场景不一样,主GUI程序根本不知道工作线程/进程的操作细节,也不知道什么时候需要弹出对话框。我知道线程间可以用信号槽、进程间可以用管道,但在这个场景里不太实用——因为要不要弹对话框、对话框的内容都是由工作线程/进程自己决定的,主线程/进程没法提前预知。
主GUI进程里显示和处理QDialog不需要额外跑Qt事件循环,但我发现在工作线程/进程里弹对话框必须要启动事件循环。我只能在QThread实例里实现这一点,但这样一来,一个线程的事件循环会把所有线程都阻塞,完全违背了多并发的初衷。
有没有办法在非主GUI线程/进程中创建并显示PyQt的QDialog,同时不会阻塞其他线程?
补充示例代码
注:以下示例省略了大量细节(比如Qt事件的调用逻辑、错误处理等),只是一个极简的单线程结构,用来说明核心需求。我的实际实现比这复杂得多,部分基于简单场景的方案并不适用。
class FseOperation(): def __init__(self, opcode, *args, **kwargs): self.opcode = opcode.upper() self.args = args self.kwargs = kwargs self.opReturn = None self.reportProgress = False class FileSystemEngine(): def operation(self, op): if op.opcode == 'COPY': self.copy(*op.args,**op.kwargs) elif op.opcode == 'SHRED': # ... 其他操作逻辑 pass def copy(self, src, dest, promptForReplace=True): destExists = True # 示例中简化判断逻辑 if destExists: if promptForReplace: w = PromptForReplaceWidget(parameters) reply, options = w.reply() if reply: rc = self.copyFile(src, dest, op.reportProgress, options) else: rc = "NO_REPLACE" else: rc = "DEST_EXISTS" else: rc = self.copyFile(src, dest, op.reportProgress) op.opReturn = rc def copyFile(self, src, dest, reportProgress=False, options=None): if reportProgress: pbar = ProgressBar() else: pbar = None # 示例中简化文件读写逻辑 EOF = False while not EOF: block = b"" # 模拟读取文件块 # writeFile(dest, block) if pbar is not None: pbar.update(len(block)) EOF = True if __name__ == '__main__': FSE = FileSystemEngine() op1 = FseOperation('COPY', 'foo.bar', 'fooBar.copy', reportProgress=True) op2 = FseOperation('COPY', 'file.txt', 'copy.txt', reportProgress=True) FSE.operation(op1) FSE.operation(op2) if op1.opReturn == 0: print('copy1 success') else: print('copy1 fail')
我希望让FSE.operation()方法在不同的任务(线程或进程)中启动,且这些任务能独立处理自己的GUI,不需要依赖主线程/进程。目前已经明确:用Python线程、进程或Qt QThreads都没法实现这个需求,可行的方向是用Python的subprocess或者Qt的QProcess来执行每个操作——这两种方式都能单独启动Qt事件循环,控制自己的GUI。
另外,工作线程和主线程用信号槽通信虽然可行,但并不符合我的场景需求,目前还在研究其他方案。
内容的提问来源于stack exchange,提问作者dave

