检查文件是否存在时阻止 "GUI Freezing"
Prevent "GUI Freezing" While Checking a File Exists
我正在尝试在 pyqt5 中构建一个文件侦听器应用程序。我的代码按我的意愿工作,但我想改进它。
有一个简单的按钮Listen
。当我单击它时,它会打开一个记事本并开始一直监听,直到 a.txt
文件存在。存在后,新按钮 Start
存在,旧按钮被删除。
我的问题是;我的 GUI 在收听 a.txt
文件时冻结 即使我使用 threading
。我用错了吗?你能修复我的代码吗?
我的主要代码;
from PyQt5 import QtCore, QtWidgets
import sys
import os
class ListenWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ListenWindow, self).__init__(parent)
self.setWindowTitle("Listen")
self.button_listen = QtWidgets.QPushButton('Listen', self)
font1 = self.button_listen.font()
font1.setPointSize(10)
self.button_listen.setFont(font1)
self.button_listen.setFixedSize(200, 50)
self.button_listen.clicked.connect(self.startToListen)
self.v_box1 = QtWidgets.QVBoxLayout(self)
self.v_box1.addWidget(self.button_listen)
self.h_box1 = QtWidgets.QHBoxLayout(self)
self.v_box1.addLayout(self.h_box1)
def abc(self):
while not os.path.exists('C:/Users/Wicaledon/PycharmProjects/myproject/a.txt'):
pass
if os.path.isfile('C:/Users/Wicaledon/PycharmProjects/myproject/a.txt'):
self.button_start = QtWidgets.QPushButton('Start', self)
font2 = self.button_start.font()
font2.setPointSize(10)
self.button_start.setFont(font2)
self.button_start.setFixedSize(200, 50)
self.h_box1.addWidget(self.button_start, 0, QtCore.Qt.AlignCenter)
else:
raise ValueError("%s isn't a file!" % 'C:/Users/Wicaledon/PycharmProjects/myproject/a.txt')
self.v_box1.removeWidget(self.button_listen)
def startToListen(self):
def thread_function(my_text):
import subprocess
import os
FNULL = open(os.devnull, 'w')
args = my_text
subprocess.call(args, stdout=FNULL, stderr=FNULL, shell=True)
# os.system(my_text)
return
import threading
my_text = "notepad"
x = threading.Thread(target=thread_function,args=(my_text,))
x.start()
y = threading.Thread(target=ListenWindow.abc(self))
y.start()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = ListenWindow()
window.setWindowTitle('Login')
window.show()
sys.exit(app.exec_())
我的创建代码 a.txt
;
f=open("a.txt", "w+")
f.write("delete_me")
f.close()
将 abc
函数中的 while
循环更改为
while not os.path.exists('b.txt'):
QtCore.QCoreApplication.processEvents()
pass
使用这种简单的 while 方法来检查文件是否存在并不是一件好事,因为它会不必要地消耗大量 CPU 资源。
此外,在处理 UI 时,使用任何类型的循环(while/for 或递归函数调用)都是一个坏主意:正如您已经看到的,它会阻塞接口;虽然 abhilb 提出的 可能看起来有效,但它只是使 UI 响应,但无论如何都会保持 CPU 尖峰,即使在程序关闭并且文件尚未被删除之后已创建。
PyQt 已经有一个文件监听器,QFileSystemWatcher,外部系统应该避免使用 Qt 已经提供的功能,这不仅对文件监听器很重要,而且对线程也很重要。
另一个需要记住的重要方面是 Qt 有自己的事件循环,使用 python 的线程与之交互并不是一个好主意(事实上,每个 UI交互都必须在主线程中完成,即使使用Qt的线程也是如此。
如果你真的想做一个基本的文件监听器(比如学习用),你至少应该在循环内添加一个服务员,那个循环有在另一个线程中,否则 GUI 无论如何都会被阻塞。
以下是完全基于Qt的实现,根据你的代码。
import sys
from PyQt5 import QtCore, QtWidgets
fileToWatch = 'C:/Users/Wicaledon/PycharmProjects/myproject/a.txt'
editorProgram = 'notepad'
class ListenWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ListenWindow, self).__init__(parent)
self.setWindowTitle("Listen")
self.button_listen = QtWidgets.QPushButton('Listen', self)
font1 = self.button_listen.font()
font1.setPointSize(10)
self.button_listen.setFont(font1)
self.button_listen.setFixedSize(200, 50)
self.button_listen.clicked.connect(self.startToListen)
self.v_box1 = QtWidgets.QVBoxLayout(self)
self.v_box1.addWidget(self.button_listen)
# no parent with an already existing layout should be set for a new
# layout; in this case, the current widget already has the v_box1 layout
# set, therefore the new h_box1 layout should have no argument
self.h_box1 = QtWidgets.QHBoxLayout()
self.v_box1.addLayout(self.h_box1)
self.listener = QtCore.QFileSystemWatcher(self)
self.listener.directoryChanged.connect(self.checkFile)
def startToListen(self):
fileInfo = QtCore.QFileInfo(fileToWatch)
if fileInfo.exists():
self.createStart()
return
elif fileInfo.absolutePath() not in self.listener.directories():
self.listener.addPath(fileInfo.absolutePath())
# create an empty file so that there's no error when trying to open
# it in the editor
emptyFile = QtCore.QFile(fileToWatch)
emptyFile.open(emptyFile.WriteOnly)
emptyFile.close()
process = QtCore.QProcess(self)
process.start(editorProgram, [fileToWatch])
# optional: disable the interface until the program has quit
self.setEnabled(False)
process.finished.connect(lambda: self.setEnabled(True))
def checkFile(self, path):
fileInfo = QtCore.QFileInfo(fileToWatch)
if fileInfo.exists():
if self.h_box1:
# the layout already contains the start button, ignore
return
else:
self.createStart()
else:
# file has been [re]moved/renamed, maybe do something here...
pass
def createStart(self):
self.button_start = QtWidgets.QPushButton('Start', self)
font2 = self.button_start.font()
font2.setPointSize(10)
self.button_start.setFont(font2)
self.button_start.setFixedSize(200, 50)
self.h_box1.addWidget(self.button_start, 0, QtCore.Qt.AlignCenter)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = ListenWindow()
window.setWindowTitle('Login')
window.show()
sys.exit(app.exec_())
我正在尝试在 pyqt5 中构建一个文件侦听器应用程序。我的代码按我的意愿工作,但我想改进它。
有一个简单的按钮Listen
。当我单击它时,它会打开一个记事本并开始一直监听,直到 a.txt
文件存在。存在后,新按钮 Start
存在,旧按钮被删除。
我的问题是;我的 GUI 在收听 a.txt
文件时冻结 即使我使用 threading
。我用错了吗?你能修复我的代码吗?
我的主要代码;
from PyQt5 import QtCore, QtWidgets
import sys
import os
class ListenWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ListenWindow, self).__init__(parent)
self.setWindowTitle("Listen")
self.button_listen = QtWidgets.QPushButton('Listen', self)
font1 = self.button_listen.font()
font1.setPointSize(10)
self.button_listen.setFont(font1)
self.button_listen.setFixedSize(200, 50)
self.button_listen.clicked.connect(self.startToListen)
self.v_box1 = QtWidgets.QVBoxLayout(self)
self.v_box1.addWidget(self.button_listen)
self.h_box1 = QtWidgets.QHBoxLayout(self)
self.v_box1.addLayout(self.h_box1)
def abc(self):
while not os.path.exists('C:/Users/Wicaledon/PycharmProjects/myproject/a.txt'):
pass
if os.path.isfile('C:/Users/Wicaledon/PycharmProjects/myproject/a.txt'):
self.button_start = QtWidgets.QPushButton('Start', self)
font2 = self.button_start.font()
font2.setPointSize(10)
self.button_start.setFont(font2)
self.button_start.setFixedSize(200, 50)
self.h_box1.addWidget(self.button_start, 0, QtCore.Qt.AlignCenter)
else:
raise ValueError("%s isn't a file!" % 'C:/Users/Wicaledon/PycharmProjects/myproject/a.txt')
self.v_box1.removeWidget(self.button_listen)
def startToListen(self):
def thread_function(my_text):
import subprocess
import os
FNULL = open(os.devnull, 'w')
args = my_text
subprocess.call(args, stdout=FNULL, stderr=FNULL, shell=True)
# os.system(my_text)
return
import threading
my_text = "notepad"
x = threading.Thread(target=thread_function,args=(my_text,))
x.start()
y = threading.Thread(target=ListenWindow.abc(self))
y.start()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = ListenWindow()
window.setWindowTitle('Login')
window.show()
sys.exit(app.exec_())
我的创建代码 a.txt
;
f=open("a.txt", "w+")
f.write("delete_me")
f.close()
将 abc
函数中的 while
循环更改为
while not os.path.exists('b.txt'):
QtCore.QCoreApplication.processEvents()
pass
使用这种简单的 while 方法来检查文件是否存在并不是一件好事,因为它会不必要地消耗大量 CPU 资源。
此外,在处理 UI 时,使用任何类型的循环(while/for 或递归函数调用)都是一个坏主意:正如您已经看到的,它会阻塞接口;虽然 abhilb 提出的
PyQt 已经有一个文件监听器,QFileSystemWatcher,外部系统应该避免使用 Qt 已经提供的功能,这不仅对文件监听器很重要,而且对线程也很重要。
另一个需要记住的重要方面是 Qt 有自己的事件循环,使用 python 的线程与之交互并不是一个好主意(事实上,每个 UI交互都必须在主线程中完成,即使使用Qt的线程也是如此。
如果你真的想做一个基本的文件监听器(比如学习用),你至少应该在循环内添加一个服务员,那个循环有在另一个线程中,否则 GUI 无论如何都会被阻塞。
以下是完全基于Qt的实现,根据你的代码。
import sys
from PyQt5 import QtCore, QtWidgets
fileToWatch = 'C:/Users/Wicaledon/PycharmProjects/myproject/a.txt'
editorProgram = 'notepad'
class ListenWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ListenWindow, self).__init__(parent)
self.setWindowTitle("Listen")
self.button_listen = QtWidgets.QPushButton('Listen', self)
font1 = self.button_listen.font()
font1.setPointSize(10)
self.button_listen.setFont(font1)
self.button_listen.setFixedSize(200, 50)
self.button_listen.clicked.connect(self.startToListen)
self.v_box1 = QtWidgets.QVBoxLayout(self)
self.v_box1.addWidget(self.button_listen)
# no parent with an already existing layout should be set for a new
# layout; in this case, the current widget already has the v_box1 layout
# set, therefore the new h_box1 layout should have no argument
self.h_box1 = QtWidgets.QHBoxLayout()
self.v_box1.addLayout(self.h_box1)
self.listener = QtCore.QFileSystemWatcher(self)
self.listener.directoryChanged.connect(self.checkFile)
def startToListen(self):
fileInfo = QtCore.QFileInfo(fileToWatch)
if fileInfo.exists():
self.createStart()
return
elif fileInfo.absolutePath() not in self.listener.directories():
self.listener.addPath(fileInfo.absolutePath())
# create an empty file so that there's no error when trying to open
# it in the editor
emptyFile = QtCore.QFile(fileToWatch)
emptyFile.open(emptyFile.WriteOnly)
emptyFile.close()
process = QtCore.QProcess(self)
process.start(editorProgram, [fileToWatch])
# optional: disable the interface until the program has quit
self.setEnabled(False)
process.finished.connect(lambda: self.setEnabled(True))
def checkFile(self, path):
fileInfo = QtCore.QFileInfo(fileToWatch)
if fileInfo.exists():
if self.h_box1:
# the layout already contains the start button, ignore
return
else:
self.createStart()
else:
# file has been [re]moved/renamed, maybe do something here...
pass
def createStart(self):
self.button_start = QtWidgets.QPushButton('Start', self)
font2 = self.button_start.font()
font2.setPointSize(10)
self.button_start.setFont(font2)
self.button_start.setFixedSize(200, 50)
self.h_box1.addWidget(self.button_start, 0, QtCore.Qt.AlignCenter)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = ListenWindow()
window.setWindowTitle('Login')
window.show()
sys.exit(app.exec_())