Как читать из файла каждый раз, когда другой другой процесс обновляет его

python #multithreading #pyqt5

#python #многопоточность #pyqt5

Вопрос:

Я работаю над своим первым приложением. В одном окне у меня есть кнопка, при нажатии на которую я хочу выполнить метод из другого модуля. Выполнение этого метода занимает неопределенное количество времени и зависит от ввода данных пользователем в терминале. Этот метод создает файл и повторно открывает его, записывает данные в файл и закрывает файл. В то же время, когда это выполняется, у меня есть графический виджет matplotlib в окне с графиком, который я хочу обновлять каждый раз, когда в файл записывается что-то новое, путем чтения и построения данных из самой последней строки файла.

Насколько я понимаю, ничто в моем приложении не будет отвечать до завершения функции пользовательского ввода, если она запущена в основном потоке моей программы QT. Чтобы решить эту проблему, я попытался перенести выполнение метода пользовательского ввода в рабочий поток. В том, как я это сделал, я не уверен, что это работает. В качестве теста я попытался создать QTimer, который пытался читать файл и отображать его каждую секунду (с некоторыми добавлениями, чтобы проверить, существует ли файл). Это выводит, что файл еще не существует до тех пор, пока не будет выполнена длинная задача, а затем ничего не делает, пока не будет выполнена длинная задача, а затем начнет чтение и построение файла каждую секунду. Я не уверен, означает ли это, что я неправильно выполняю потоки или происходит что-то еще.

Чтобы проверить наличие изменений в файле, я попытался использовать QFileSystemWatcher. ОБНОВЛЕНИЕ: прямо сейчас ничего не происходит во время выполнения userInputFunction(), но когда он завершается, я получаю «data/runName_Rec.txt Файл данных создан». Если я затем вручную отредактирую файл каким-либо образом, построение графика происходит так, как должно. Но я все еще хочу правильно его обработать, чтобы наблюдатель работал, пока я запускаю userInputFunction()

Вот упрощенный пример соответствующих частей моего кода. Извините за любые проблемы с плохим стилем.

 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__)
class Worker(QObject):
    finished = pyqtSignal()

    def run(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.startSliceScan)
        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):
        # Step 2: Create a QThread object
        self.thread = QThread()
        # Step 3: Create a worker object
        self.worker = Worker()
        # Step 4: Move worker to the thread
        self.worker.moveToThread(self.thread)
        # Step 5: Connect signals and slots
        self.thread.started.connect(self.worker.run(param1,param2))
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)
        # Step 6: Start the thread
        self.thread.start()

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    w = someWindow()
    w.show()
    sys.exit(app.exec_())
 

Ответ №1:

Как вы объяснили, при запуске вашей программы рассматриваемый файл еще не существует.

Из документации QFileSystemWatcher:

Добавляет путь к наблюдателю файловой системы, если путь существует. Путь не добавляется, если он не существует или если он уже отслеживается наблюдателем файловой системы.

Решение вашей проблемы двоякое:

  1. Чтобы получать уведомления о вновь созданном файле, добавьте родительский каталог (т. Е. Каталог, В котором вы ожидаете появления файла) в ваш наблюдатель (в вашем случае просто ‘.’). Затем вы получите уведомление (сигнал directoryChanged) при создании любых новых файлов. Ваш update_graph метод уже подготовлен к этой динамике, поскольку он проверяет наличие вашего целевого файла; поэтому вы можете напрямую подключить его directoryChanged , как вы это делали с fileChanged .
  2. Как только файл появится, чтобы получать уведомления о любых будущих изменениях в файле, добавьте сам файл в наблюдатель, как вы уже делали, но на этот раз также сделайте это update_graph (это может показаться излишним, но обратите внимание, как в приведенной выше цитате, избыточное добавление файла не является ошибкой).проблема; также, когда вы добавляете путь при запуске, вы закрываете случай, когда файл уже есть).
  3. В случае удаления файла вы снова получите событие directoryChanged . Вам не нужно удалять путь к удаленному файлу.
  4. Наконец, обратите внимание, что addPath() возвращает bool success . Поэтому вы должны проверить возвращаемое значение в своей программе.

Причина такого поведения заключается в том, что при вызове addPath() файл или каталог, найденный по этому пути, напрямую добавляется в наблюдатель (а не путь, указывающий на него). Таким образом, уведомление работает только в течение срока службы файла (или каталога).

Комментарии:

1. Это имеет смысл. Пожалуйста, посмотрите мое последнее редактирование, я пытался это реализовать. Я думаю, что логика наблюдателя здесь выглядит хорошо. Однако это все равно не работает, потому что весь графический интерфейс заморожен во время выполнения процесса MyModule.userInputFunction() . Поскольку единственный раз, когда каталог /data или файл изменяются, когда графический интерфейс заморожен, график никогда не обновляется.

2. Мне кажется, что этот путь неверен.. self.fs_watcher.addPath("/data")

3. О, да, вы правы! Я отредактировал свой пост. Теперь ничего не происходит во время выполнения userInputFunction(), но когда он завершается, я получаю «data/runName_Rec.txt Файл данных создан». Если я затем вручную отредактирую файл каким-либо образом, построение графика происходит так, как должно. Но я все еще хочу правильно его обработать, чтобы наблюдатель работал, пока я запускаю userInputFunction()

4. Я рад, что ваша проблема, обсуждаемая в этом вопросе, решена. Я предлагаю вам открыть другой вопрос для вашей проблемы с потоками.

5. Я задал новый вопрос только о проблеме с потоками. Поскольку мой первоначальный вопрос также касался потоков, и разрешена только часть QFileSystemWatcher, было бы неплохо отредактировать этот вопрос, чтобы он касался только QFileSystemWatcher? Спасибо