QFileSystemWatcher / Widget repaint

Alles rund um die Programmierung mit Qt
Antworten
pfid
Beiträge: 535
Registriert: 22. Februar 2008 16:59

QFileSystemWatcher / Widget repaint

Beitrag von pfid »

Hallo,

ich nutze in meinem Programm einen QFileSystemWatcher, um die Größe einer Datei zu beobachten. Diese Datei wird bei der Ausführung des Programms geschrieben, und kann beliebig groß sein.

Um das ganze optisch darzustellen hab ich eine QProgressBar platziert, deren Value im Slot ::onFileChanged(const QString& file) mit QFileInfo::size() gesetzt wird.

Nun hab ich damit 2 Probleme. Zum einen wird die QProgressBar nicht aktualisiert, wenn sich die Größe der Datei ändert. Zum Debuggen habe ich dann mal eine QMessageBox eingebaut, die mir bei ::onFileChanged() die fileInfo->size() ausgibt. Während der Ausführung bekomm ich dann ein paar Popups, mit jeweils der korrekten aktuellen Größe der Datei, und siehe da: die ProgressBar wächst! Nehm ich die QMessageBox wieder raus, ändert sich die ProgressBar nicht - sie bleibt auf 0% stehn (unabhängig von der Datei, und ist auch reproduzierbar). Habe dann statt der QMessageBox (welche bei ner 2GB Datei bisschen nervig wegzuklicken is..) mal ein repaint() (aufs QMainWindow) oder ein progressBar->repaint() versucht, bringt nichts, bleibt stur auf 0% stehn.

Das andere Problem ist der QFileSystemWatcher selbst. Bei einer 35MB Datei bekomm ich 2x ::onFileChanged(), das ist zu wenig um eine flüssige Fortschrittsanzeige darzustellen.
Die Datei wird in einem eigenen Thread geschrieben. Dabei wird eine Inputdatei gelesen, gepuffert, und dann in die Ausgabedatei geschrieben. Etwa so:

Code: Alles auswählen

   std::ifstream in(inputFileName, std::ios_base::binary);
   std::ofstream out(outputFileName);

      while (in.good())
      {
         in.read((char*)buffer.begin(), buffer.size());

         if (in.eof())
            // fertig

          // buffer verarbeiten

         out.write((const char*)buffer.begin(),  size);
         out.flush();
      }
Der Buffer ist momentan 1024byte groß.

Kann man irgendwie erreichen, dass der Watcher schneller/öfter reagiert? Falls nicht, hat jemand eine andere Idee wie ich eine Fortschrittsanzeige realisieren könnte? Habe beim Verarbeiten der Datei im Thread leider zZ keine Möglichkeit den Status an den Mainthread zu melden.
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Beitrag von Christian81 »

Widget repaint ist nur möglich wenn Qt auch mal Zeit hat die Events in der Eventloop abzuarbeiten.
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
pfid
Beiträge: 535
Registriert: 22. Februar 2008 16:59

Beitrag von pfid »

Christian81 hat geschrieben:Widget repaint ist nur möglich wenn Qt auch mal Zeit hat die Events in der Eventloop abzuarbeiten.
Und wieso hat Qt die Zeit bei mir nicht?
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Beitrag von Christian81 »

Wile Du in einer loop bist und Qt keine Zeit gibst?

Foren-suche, ausserdem Qt-Doku
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
ObeliX
Beiträge: 59
Registriert: 14. November 2007 17:47

Beitrag von ObeliX »

um ggf. die wilde sucherei etwas abzukürzen :) :
ich würd mal veruchen ob QCoreApplication::processEvents() hilft.

gruß Obel
pfid
Beiträge: 535
Registriert: 22. Februar 2008 16:59

Beitrag von pfid »

Puh, also ich verstehs nicht. Habs jetzt ein paar mal hinbekommen, dass der Balken bis zu 100% läuft, was aber spätestens nach einen Programm neustart bzw. erneutem Versuch (ohne Neustart) nicht mehr richtig (bis ~20%) oder gar nicht mehr funktioniert hat.

Ich versteh nicht, wieso der Thread die EventLoop vom Mainthread blockiert, und wieso der Mainthread während der File-Thread läuft - ohne QCoreApplication::processEvents() - alles macht (hide(), show(), MessageBox, KeyEvent), bis auf das Refreshen der QProgressBar .. :?:

Laut Thread Doku kann der QThread eine eigene EventLoop haben, muss er bei mir in meinen Augen aber nicht, da in meiner Thread-Klasse keine weiteren QObjects sind, die ne EventLoop brauchen. Demzufolge rufe ich kein QThread::exec() auf. Der Thread wird vom GUI-Thread per MeinThread::start() gestartet. In MeinThread::run() befindet sich nur 1 Aufruf, MeinThread::processFile(), keine Schleife. D.h. nachdem MeinThread::processFile() fertig ist, sollte sich der Thread (nach meinem Verständnis) beenden, da MeinThread::run() zuende ist.

Wenn ich in MeinThread::run() ein exec() aufrufe, passiert gar nichts mehr; der Thread tut einfach nichts.

Kann mir das jemand irgendwie verständlich näher bringen? Ich komme irgendwie nicht weiter...

@forensuche: in den meisten beispielen die ich gefunden habe gehts um was anderes; dort wird das gui-objekt auf irgendeine weise vom neben-thread geändert. das ist bei mir nicht der fall. der file-thread wird einmal angestoßen, und interagiert dann nicht mehr mit dem gui-thread, bis er am ende von run() ein signal feuert und dem gui-thread bescheid sagt. der gui-thread wiederrum watcht nur die datei, während der file-thread läuft.
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

Häng mal bitte ein komplettes Beispiel an. Auf die Funktion beschränkt, so dass wir es reproduzieren können. Denn mit den Beschreibungen allein werden wir jetzt nicht weiter kommen.

Grüße
Franz
pfid
Beiträge: 535
Registriert: 22. Februar 2008 16:59

Beitrag von pfid »

Thread Header:

Code: Alles auswählen

#include <QThread>

class MyThread : public QThread
{
   Q_OBJECT

public:

   MyThread ();
   ~MyThread ();

   int init(const char* inFile, const char* outFile, int mode);

   int cancel() { cancelFile = 1; return 0; }

   const char* processFile(const char* inputFileName, const char* outputFileName);

private:

   char inputFileName[256+1];
   char outputFileName[256+1];

   int operationMode;
   int cancelFile;
   
protected:

   void run();

public slots:

signals:

   void threadFinished(const char* errorMsg, int mode);

};
thread cpp:

Code: Alles auswählen

MyThread ::MyThread ()
{
   *inputFileName = 0;
   *outputFileName = 0;
   cancelFile = 0;
}

MyThread ::~MyThread ()
{
}

void MyThread ::run()
{
   const char* result = 0;

   switch (operationMode)
   {
      case 0:     result = processFile(inputFileName, outputFileName); break;

      default: break;
   }
   
   emit threadFinished(result, operationMode);
}

const char* MyThread ::processFile(const char* inputFileName, const char* outputFileName)
{
   std::ifstream in(inputFileName, std::ios_base::binary);
   std::ofstream out(outputFileName);

   if (!in)
      return "Couldn't open input file";

   if (!out)
      return "Couldn't open output file";

      while (in.good() && !cancelFile)
      {
         in.read((char*)buffer.begin(), buffer.size());

        // do something

         out.write((const char*)buffer.begin(), buffered);
         out.flush();
      }

      if (in.bad() || (in.fail() && !in.eof()))
         return "Input file I/O error";

   if (cancelFile)
      return "User canceled.";

   return 0;
}


GUI-Klasse:

Code: Alles auswählen

#include "mythread.h"

class MyThread;

class MyWindow : public QMainWindow
{
   Q_OBJECT

public:

   MyWindow ();
   ~MyWindow ();

   int init();
   int exit();

   int enableUi();
   int disableUi();

   void setVisible(bool visible);
   void keyPressEvent(QKeyEvent* event);

private:

     // jede menge UI QObjects


   MyThread* myThread;

public slots:

   void onFileChanged(const QString& file);
   void onThreadFinished(const char* errorMsg, int mode);
};
GUI cpp

Code: Alles auswählen

MyWindow::MyWindow()
{
   settings = 0;
   myThread = 0;
   fileInfo = 0;
}

MyWindow::~MyWindow()
{
   exit();

   delete myThread;
   delete settings;
   delete fileInfo;
}

int MyWindow::init()
{
   fileInfo = new QFileInfo();
   fileWatcher = new QFileSystemWatcher(this);

   myThread = new MyThread();
   connect(myThread, SIGNAL(threadFinished(const char*, int)), this, SLOT(onThreadFinished(const char*, int)));
   
   // jede menge gui-zeug

   return 0;
}

void MyWindow::onFileChanged(const QString& file)
{
      fileInfo->setFile(file);
      progressBar->setValue(fileInfo->size()); 

      //update();
      repaint();

      QCoreApplication::processEvents();

      //QMessageBox::information(this, "info", QString::number(fileInfo->size()));
  
}


void MyWindow::onPushButtonStart()
{

   progressBar->setMaximum(fileInfo->size()*1.35);
   fileWatcher->addPath(lineEditOutFile->text());

   myThread->init(infile, outfile, mode;

   myThread->start();
}



GUI-Programm is stark gekürzt, aber sollte das relevante dabei sein.

Ich sollte noch anmerken, dass sichergestellt ist, dass die outputdatei existiert, bevor der Watcher aktiviert wird. Die Signale kommen korrekt, und geben die korrekte Dateigröße bei mir aus. Wenn die Datei nicht da wäre, würde der Watcher den Pfad nicht annehmen, und keine Signale abschicken.
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

Wo setzt du die Minimum und Maximum-Werte für die Progressbar?
pfid
Beiträge: 535
Registriert: 22. Februar 2008 16:59

Beitrag von pfid »

franzf hat geschrieben:Wo setzt du die Minimum und Maximum-Werte für die Progressbar?
Ganz unten, hier:

Code: Alles auswählen

void MyWindow::onPushButtonStart()
{

   progressBar->setMaximum(fileInfo->size()*1.35); 

die datei wird b64 encoded, daher ist die output datei etwa 30-40% größer als die input datei. bisschen ungenauigkeit stört nicht, wenns fertig is wirds eh aufs maximum gesetzt. ausserdem ist der watcher so langsam, dass man eh nur 10-20%-Schritte zu sehen bekommt.

minimun setze ich nicht explizit (da default 0 okay ist).
pfid
Beiträge: 535
Registriert: 22. Februar 2008 16:59

Beitrag von pfid »

Okay, habe eben rausgefunden dass es wohl was mit der Plattform zu tun hat, und der Code an sich korrekt zu sein scheint. Ich habe das Programm mal ohne repaint(), update() oder processEvents() unter Linux durchkompiliert, und da läuft das ganze wunderbar. Der Watcher ist superschnell, bekomme bei Files mit 100kb oder so schöne 1%-Schritte angezeigt, und der Balken läuft immer wie er soll.
Unter Windows tut sich allerdings nichts.

Auf der Linux Kiste hab ich Kubuntu Feisty, und per apt-get ist das 4.1er Qt installiert. Auf der Vista x64 Kiste hab ich die 4.3.4 Sourcen (ohne Mingw) mit -static -win32-msvc2005 gebaut. Habs in nem parallelen Verzeichnis nochmal ohne -static liegen, macht aber keinen Unterschied (auf das Problem bezogen), wie ich linke.

Hatte schon ganz am Anfang beobachtet, als ich noch das GUI für die Applikation gebaut habe, dass das Programm unter Windows ziemlich langsam ist. Menüs und Dialoge gehen mit einer Verzögerung auf, wenn ich Radiobuttons im wechsel anklicke, kann ich sehn wie die Punkte ein- und ausfaden :?:

Hat jemand ne Idee woran das liegen könnte? Die Vista Machine ist der Linuxkiste leistungsmäßig eig. ziemlich überlegen, hab momentan nur kein *NIX drauf zum parallel testen (werd ich demnächst ändern).
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Beitrag von Christian81 »

Das connect mit const char* funktioniert auch nur durch Zufall - sobald der String in irgend einer Form dynamisch ist, crasht es -> QString benutzen!
Auch die ganzen anderen char* Spielereien sind ... nunja suboptimal. Vor allem wenn deine Datei Umlaute bzw. non-Ascii Zeichen beinhalten. Ich würde auch auf std:: verzichten und QFile benutzen.

Ansonsten sehe ich jetzt kein Problem mehr. Sollte ohne update()/processEvents() funktionieren. Wurde Qt bzw. deine App im Debug-Mode gebaut? Wie ist die CPU-Auslastung? Hilft es wenn der Thread zwischenzeitlich etwas schläft -> sleep(1) oder so in Thread-Loop einbauen um zu schauen ob es sich dann besser verhält.
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
pfid
Beiträge: 535
Registriert: 22. Februar 2008 16:59

Beitrag von pfid »

Christian81 hat geschrieben: Ansonsten sehe ich jetzt kein Problem mehr. Sollte ohne update()/processEvents() funktionieren. Wurde Qt bzw. deine App im Debug-Mode gebaut? Wie ist die CPU-Auslastung? Hilft es wenn der Thread zwischenzeitlich etwas schläft -> sleep(1) oder so in Thread-Loop einbauen um zu schauen ob es sich dann besser verhält.
Ob Debug oder Release kommt aufs gleiche raus. Ein Core geht auf 100% während das Programm läuft. Das sleep hat nichts geändert.

Habe nun den selben Code mehrmals auf Kubuntu feisty x86, Windows XP x86, FreeBSD 6.3 x86 und Vista x64 getestet.

- auf Vista bewegt sich der balken keinen milimeter, egal wie groß die datei ist
- auf xp geht die bar halbwegs. kleinere dateien (~30mb) machen 2 sprünge, 30% und 70%, dann 100%. noch kleinenere dateien sind instant auf 100%. große dateien (mehrere hundert mb) zeigen dann 2% schritte, der balken selbst wurde bei meinem testfile ab 50% sichtbar (die % anzeige lief in 2%-schritten, der balken war aber noch auf 0%), lief dann bis ~60%, ging dann wieder, und ist dann auf 100% gesprungen als die datei fertig war
- unter kubuntu & fbsd funktioniert die bar einwandfrei. selbst kleine files laufen mit guter genauigkeit mit. die anzeige ist präzise, hängt nirgends

Muss dazu sagen, dass ich unter fbsd und kubuntu die 4.3.1 nutze, unter Vista hab ich die 4.3.4 kompiliert.


[edit]
Hab mir jetzt mit nem QTimer beholfen, der alle 50ms die Output Filesize pollt. Das Funktioniert auch unter Windows. Wie klein darf man das Intervall den machen, damit das ganze noch gesund ist? Mit 100ms bekomm ich bei kleineren Files nicht mehr viel mit. Hab mal testweise 5ms genommen, da bekommt man feinere Schritte... in Sacvhen CPU Auslastung gibt sich das imho nicht viel, zumindest bei dem was mir der Taskmanager so anzeigt. Aber keine Ahnung wie das auf schwächeren/single core Kisten aussieht.
Antworten