如何在PyQt5中正确执行多线程,同时读取和写入文件?



在一个窗口中,我有一个按钮,当单击时,我想执行来自另一个模块的方法。此方法执行所需的时间不确定,并且取决于终端中的用户输入。这个方法创建一个文件并反复打开它,向文件写入内容,然后关闭文件。与此同时,我在窗口中有一个matplotlib图形小部件,其中包含一个图,每次通过从文件的最新行读取和绘制数据向文件写入新内容时,我要更新该图。

检查对文件的更改,我使用QFileSystemWatcher。现在,当userInputFunction()运行时没有任何事情发生,但是当它完成时,我得到"data/runName_Rec.txt dataFileCreated"。如果我以任何方式手动编辑文件,绘图就会像它应该的那样发生。因此,似乎监视程序只是在userInputFunction()完成后才开始工作,并看到目录中已经发生了变化。

我如何正确地做多线程,使观察者的工作,而userInputFunction()正在运行?

据我所知,如果我让它在QT程序的主线程中运行,那么在用户输入函数完成之前,应用程序中的任何内容都不会响应。为了解决这个问题,我尝试将用户输入法的执行移到一个工作线程中,如下面的示例:https://realpython.com/python-pyqt-qthread/。在这个工作中,我有两个方法。只需使用sleep()执行for循环,该循环需要一段时间,与示例完全相同。另一个运行我的userInputFunction()。for循环方法run()不会冻结GUI。但是,执行我想要的实际进程的runScan()仍然会冻结GUI。我不知道这是怎么回事。我不确定这是否意味着我没有正确执行线程,或者是否有其他事情发生。

下面是我代码中相关部分的简化示例。

from PyQt5 import QtWidgets, uic, QtCore, QtGui
from pyqtgraph import PlotWidget
from PyQt5.QtCore import QObject, QThread, pyqtSignal
import pyqtgraph as pg
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QInputDialog, QLineEdit, QFileDialog, QMainWindow
import os
from os.path import exists
import csv
import numpy as np
import pandas as pd
import myModule
dirname = os.path.dirname(__file__)
# Create a worker class
class Worker(QObject):
finished = pyqtSignal()
#This works without freezing the GUI
def run(self):
"""Long-running task."""
for i in range(5):
time.sleep(1)
print("step ",i," done!")
self.finished.emit()
#This freezes the GUI
def runScan(self,param1,param2):
"""Long-running task with user input from terminal."""
myModule.userInputFunction(param1,param2)
self.finished.emit()

class someWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(someWindow, self).__init__(*args, **kwargs)
#Load the UI Page
uic.loadUi('somewindow.ui', self)
self.directoryPath = "data"
self.fs_watcher = QtCore.QFileSystemWatcher()
self.fs_watcher.addPath(self.directoryPath)
self.fs_watcher.directoryChanged.connect(self.dataFileCreated)

self.StartScanButton.clicked.connect(self.runLongTask)
self.EndScanButton.clicked.connect(self.endScan)
def dataFileCreated(self):
self.filePath = os.path.join(dirname, "data/"+ self.runNameBox.toPlainText()+"_Rec.txt")
print(self.filePath + "dataFileCreated")
self.fs_watcher.addPath(self.filePath)
self.fs_watcher.fileChanged.connect(self.update_graph)
def update_graph(self):
if exists(self.path):
print("file exists!")
#then read the filePath.txt and plots the data
else:
print("file doesn't exist yet")
def endScan(self):
#change some display things
def runLongTask(self):
# Create a QThread object
self.thread = QThread()
# Create a worker object
self.worker = Worker()
# Move worker to the thread
self.worker.moveToThread(self.thread)
# Connect signals and slots
#This results in the GUI freezing
self.thread.started.connect(self.worker.runScan(self.param1,self.param2))
#This DOES NOT result in the GUI freezing
#self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
# Start the thread
self.thread.start()
# Final resets
self.thread.finished.connect(
lambda: print("long task done!")
)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = someWindow()
w.show()
sys.exit(app.exec_())

在您的情况下,GUI冻结是因为这一行:

self.thread.started.connect(self.worker.runScan(self.param1,self.param2))

这导致runScan从主线程执行,阻塞任何直到完成,包括connect

这也是一个严重的错误,因为connect总是期望一个可调用的参数,一旦runScan最终完成它的工作,它返回None,你的程序将崩溃。

假设这些参数是在创建线程时添加的,您可以将它们添加到Worker构造函数中,然后在run中执行所需的代码:

class Worker(QObject):
finished = pyqtSignal()
def __init__(self, param1, param2):
super().__init__()
self.param1 = param1
self.param2 = param2
def run(self):
myModule.userInputFunction(self.param1, self.param2)
self.finished.emit()

class someWindow(QtWidgets.QMainWindow):
# ...
def runLongTask(self):
self.thread = QThread()
self.worker = Worker(self.param1, self.param2)
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)

注意,它不像QFileSystemWatcher "stop "当处理时:问题是,通过在主线程中运行runScan,你完全阻塞了主线程(不仅仅是UI),阻止了监视器处理操作系统发送给它的事件,以通知有关更改。