QThread bringt mich zur verzweiflung

Alles rund um die Programmierung mit Qt
Antworten
nisa06
Beiträge: 11
Registriert: 22. Mai 2008 11:10

QThread bringt mich zur verzweiflung

Beitrag von nisa06 »

nachdem ich nun schon einen ganzen tag in der aktuellen Qt-doc und den installierten beispielen gestoebert habe und zudem in diversen foren gelesen habe, bin ich noch verwirrter als vorher und hoffe auf einen entsprechenden tip von euch:

ich moechte in einem getrennten Thread eine grosse Textdatei einlesen. Hierzu habe ich (entgegen der "you are doing it wrong" philosophie) QThread abgeleitet und in der run methode das ganze einlesen vorgenommen.

urspruenglich wollte ich in jedem einlese-schritt per signal slot das eingelesene datum (in diesem fall eine text-zeile aus der datei) an die gui (den hauptthread) schicken. das hat aber die gui aufgrund von sehr vielen emits aufgehangen.

mein jetziger ansatz sendet nicht nach jedem einlese-schritt ein signal sondern schiebt alles eingelesene in ein QTextDocument ein (wurde im abgeleiteten thread auf dem heap erzeugt) und schickt ganz am ende des einlesens ein signal an gui-thread, dass das einlesen zu ende ist.

nun meine frage: wie komme ich an das QTextDocument ran ??

bei meinen bisherigen versuchen gab es crashes aufgrund der tatsache dass thread uebergreifend gui geaendert wird, worauf ich mich in bezug auf QObject::moveToThread(QThread *) versucht habe aber immer noch scheitere:

mein code sieht in etwa so aus (ich schicke nur relevante teile und moechte niemanden mit dem kompletten zeug erschlagen, hoffe es hilft):

worker-thread-klasse:

Code: Alles auswählen

  
  // //////////////////////////////////////////
  
  class FileLoader : public QThread
  
  // //////////////////////////////////////////
  
  {
  
  Q_OBJECT
 
  // //////////////////////////////
  public: 
  // //////////////////////////////
  
  FileLoader(QObject *parent);
  
  ~FileLoader();
 
  signals:
  void WorkDone(QTextDocument *);
  
  protected: 
  void run();
  
  // //////////////////////////////
  private:
  // //////////////////////////////
  
  CLogFile logfile;
  QTextDocument *doc;
  
  };
implementierung von run():

Code: Alles auswählen

 // //////////////////////////////////
 void FileLoader::run()
 // //////////////////////////////////
 {
	string content;

	// ...
	doc->moveToThread(this);
	QTextCursor cursor(doc);

	// hier einlesen und text mit cursor einfuegen ...
	emit WorkDone(doc);
 }
das WorkDone signal ist an einen slot PutDoc aus meinem haupt-gui-thread gebunden:

Code: Alles auswählen

      void PutDoc(QTextDocument *doc)
      {
      doc->moveToThread(QApplication::instance()->thread());
      ui->textEdit->setDocument(doc);
      }
meine application stuerzt immer bei ausfuehrung von setDocument(doc) ab. Ich vermute dass hat mit der thread-uebergreifenden verwendung/manipulation von gui-elementen zu tun und dass ich das mit dem moveToThread nicht ganz richtig mache.

nur verstehe ich weder die doku hierzu noch gibt es nirgends ein anstaendiges beispiel wie so eine "Datenuebertragung" von statten gehen muss.

ich bitte um tips / ideen / links.
vielen dank.
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

Wie groß ist denn die Datei? Hast du es schon ohne Thread versucht? Datei lesen sollte eigentlich recht flink sein.

Wieso erstellst du ein QTextDocument, wenn es nur um einige Datum-Werte geht? Reicht da nicht ne QStringList?

Und eigentlich solltest du IMMER, wenn dein Programm abstürzt oder was unvorhergesehenes macht, die Ausgabe (auf die Konsole) mitposten, im Falle eines Absturzes auch den Backtrace deines Debuggers. So kann man jetzt eigentlich gar nichts sagen.

Etwas mehr Code (wie schauen z.B. Konstruktor und Destruktor deines Threads aus? Wo und wie connectest du Hauptthread<->Workerthread?) wäre auch nicht schlecht.
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Re: QThread bringt mich zur verzweiflung

Beitrag von solarix »

nisa06 hat geschrieben:Hierzu habe ich (entgegen der "you are doing it wrong" philosophie) QThread abgeleitet und in der run methode das ganze einlesen vorgenommen.
Warum? Es könnte sein, dass du genau deswegen Probleme hast..

Hier:

Code: Alles auswählen

void PutDoc(QTextDocument *doc)
{
      doc->moveToThread(QApplication::instance()->thread());
      ui->textEdit->setDocument(doc);
} 
gibt es ganz sicher ein Problem: entweder wird der Slot im Thread-Kontext ausgeführt (dann ist "setDocument()" illegal, weil GUI-Operationen im Thread-Kontext nicht erlaubt sind) oder er wird im GUI-Kontext ausgeführt, aber dann ist "moveToThread()" illegal, weil ein QObject nicht "geklaut", sondern nur "verschenkt" werden kann (aktueller Kontext muss ungleich dem Argument von moveToThread() sein).

Die Frage ist daher: machst du auch ein "moveToThread()" des FileLoaders?
nisa06
Beiträge: 11
Registriert: 22. Mai 2008 11:10

...

Beitrag von nisa06 »

folgendes nochmal zum hintergrund:
es handelt sich bei der datei um eine text datei (logfile) was mehrere 100mb gross sein kann. das einlesen geschieht wie gesagt in einem thread. hierbei habe ich bereits folgende ansaetze probiert, die NICHT zufriedenstellend waren:
1. bei jedem einlese-schritt per signal slot das aktuelle einlese-datum (eine zeile der datei) an main-gui geschickt und ins QTextEdit gepastet

2. es wird erst ein signal an die main-gui gesendet, wenn alles eingelesen ist und der gesamte lese-inhalt (QString) übersendet.

nachteile beider ansaetze:
1. die gui friert ein, da sie alle naselang ein signal abarbeiten muss (es wird halt recht schnell eingelesen, selbst bei groesseren chunks (mehrere 100 oder 1000 zeilen) bekomme ich beim einfuegen in das QTextEdit ein problem (crasht mir aufgrund der imensen string-laenge).

2. aehnlich wie bei 1. ist der uebermittelte (riesige) QString schlecht einzufuegen (anwendung crasht bei insertPlainText(..)

Folgender (aktuellster) Ansatz wurde mir nahegelegt, jedoch fuehrt dieser wie gesagt auch zu einem crash:

Es wird beim einlesen im worker-thread ein QTextDocument befuellt. Dieses muss nun in den main-thread (an die gui) uebermittelt werden.
das einfuegen/einlesen klappt problemlos. jedoch crasht meine anwendung wenn ich in der main-gui das vom worker-thread modifizierte QTextDocument als aktuelles dokument setzen moechte.
Die QObject::moveToThread(..) methode ist in diesem kontext wichtig, bin mir aber nicht sicher ob ich sie richtig verwende.
Hier mein Code zu meiner fileloader klasse (die CLogfile Klasse kann ignoriert werden. die GetNextLines(lines, x) methode liefert aus einer textdatei die naechsten x zeilen und speichert sie in lines):

Code: Alles auswählen


// //////////////////////////////////////////
class FileLoader : public QThread
// //////////////////////////////////////////
{
   Q_OBJECT

  // //////////////////////////////
  public:
  // //////////////////////////////
    FileLoader();
    ~FileLoader();

    void Abort(){abort=true;}

  signals:
    void Progress(double);
    void WorkDone(QTextDocument *);


  protected:
    void run();

  // //////////////////////////////
  private:
  // //////////////////////////////
    CLogFile logfile;
    QTextDocument *doc;
    bool abort;
};

Code: Alles auswählen

//// //////////////////////////////////
FileLoader::FileLoader()
// //////////////////////////////////
{
  doc = new QTextDocument();
}

//// //////////////////////////////////
FileLoader::~FileLoader()
// //////////////////////////////////
{
}

// //////////////////////////////////
void FileLoader::run()
// //////////////////////////////////
{
  abort = false;

  logfile.Open("big.log");

  if( !logfile.IsOpen() )
    return;

  doc->moveToThread(this);
  QTextCursor cursor(doc);

  double rate = 0 ;
  ulong filesize = logfile.GetFileSize();
  string content  ;
  while(logfile.GetNextLines(content, 1000))
  {
    cursor.movePosition(QTextCursor::End);
    cursor.insertText(content.c_str());
    content.clear();

    if(abort)
      break;

    ulong gc = logfile.GCount();
    rate = (double)gc/(double)filesize;
    rate *= 100;
    emit Progress(rate);
  }
  emit WorkDone(doc);
  logfile.Close();
}
meine main-gui sieht so aus:

Code: Alles auswählen

// //////////////////////////////////////////
class MainWindow : public QMainWindow
// //////////////////////////////////////////
{
    Q_OBJECT

  // //////////////////////
  public:
  // //////////////////////
  explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

  public slots:
    void UpdateFileLoadProgress(double);
    void PutDoc(QTextDocument *);

  // //////////////////////
  private:
  // //////////////////////
    Ui::MainWindow *ui;
    FileLoader fileloader;
    QProgressBar progressbar;

  // //////////////////////
  private slots:
    // //////////////////////
    void on_pushButton_2_clicked();
    void on_pushButton_clicked();
};
und inhalt der entsprechenden cpp-datei:

Code: Alles auswählen

// //////////////////////////////////
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
// //////////////////////////////////
{
    ui->setupUi(this);

    QObject::connect(&fileloader,   SIGNAL(Progress(double)), this, SLOT(UpdateFileLoadProgress(double)));
    QObject::connect(&fileloader,   SIGNAL(WorkDone(QTextDocument *)),this, SLOT(PutDoc(QTextDocument *)));

    statusBar()->addPermanentWidget(&progressbar);
    progressbar.setVisible(false);
    progressbar.setRange(0,100);
}

// //////////////////////////////////
MainWindow::~MainWindow()
// //////////////////////////////////
{
  delete ui;
}

// //////////////////////////////////
void MainWindow::PutDoc(QTextDocument *doc)
// //////////////////////////////////
{
  if(!doc)
    return;

  doc->moveToThread(QApplication::instance()->thread());
  ui->textEdit->setDocument(doc);

  progressbar.setVisible(false);
}

// //////////////////////////////////
void MainWindow::UpdateFileLoadProgress(double rate)
// //////////////////////////////////
{
  progressbar.setValue((ulong)rate);
}

// //////////////////////////////////
void MainWindow::on_pushButton_clicked()
// //////////////////////////////////
{
  ui->textEdit->clear();
  progressbar.setVisible(true);
  fileloader.start();
}

// //////////////////////////////////
void MainWindow::on_pushButton_2_clicked()
// //////////////////////////////////
{
  fileloader.Abort();
}
crashen tut es in der slot-methode PutDoc(..) in der zeile

Code: Alles auswählen

ui->textEdit->setDocument(doc);
den trace habe ich als anhang (gif-bild) eingefuegt.
Dateianhänge
qt.GIF
qt.GIF (36.43 KiB) 4672 mal betrachtet
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

Schau mal was Solarix noch geschrieben hat.

Ich bin davon ausgegangen, dass ein QTextDocument als abstrakter(er) Datencontainer noch nichts mit Gui zu tun hat (so lange keine View dranhängt). Ich mach aber zugegebenermaßen zu wenig mit QThreads, so dass ich hierzu erst mal nen Test machen müsste :P

Wieso denkst du nur in Extremen? Jedes mal oder alle auf einmal. Wie wäre es mit "100 Einträge sammeln und verschicken"? Oder 1000? Mit etwas Hirnschmalz kannst du auch messen, wieviel Zeit die Gui zwischen den Signalen warten muss, und entsprechend dem Thread sagen, er soll bitte weniger oder mehr schicken.

Zu guter letzt:
Es gibt noch QCoreApplication::processEvents(). Das kannst du selber regelmäßig aufrufen, um die Gui daran zuhindern einzufrieren.
nisa06
Beiträge: 11
Registriert: 22. Mai 2008 11:10

Beitrag von nisa06 »

also ehrlich gesagt fehlen mir zu dem thema ein wenig die ideen:

das problem ist den text-inhalt einer recht grossen datei in ein text-edit zu bekommen ohne die gui in der zwischenzeit einfrieren zu lassen.
um die benutzung von threads komme ich nicht herum.
nur wie die daten an die main-gui schicken !?

nachteil von kleinen haeppchen ist, dass entsprechende signale die vom workerthread and die gui kommen (aufgrund queued-connection) von dieser zwar verarbeitet werden, aber diese aufgrund der fuelle von signals trotzdem einfriert.

bei groesseren happen habe ich wiederrum das problem des einfuegens des happens in das QTextEdit, das dauert naemlich bei entsprechender QString groesse auch recht lang (oder es gibt nen programm-absturz falls dieser happen zu gross ist)

was waere eurer meinung nach der beste ansatz zu diesem problem
!?!?! es muss doch eine allgemeine gaengige vorgehensweise geben ???

danke. :oops:
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Beitrag von Christian81 »

Die Frage ist - was wird alles mit der Datei beim Einlesen gemacht? D.h. wo wird die Zeit verbraten. Ich denke sie geht nur beim Einfügen in das Widget drauf. Da kann man so nicht viel machen ausser nicht alles anzuzeigen, mit setUpdatesEnabled() rumzuspielen oder ein einfacherers Widget zu benutzen (QPlainTextEdit). Aber um da was genauer zu sagen sind zu wenig Infos da.
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
nisa06
Beiträge: 11
Registriert: 22. Mai 2008 11:10

Beitrag von nisa06 »

es wird mit der datei selber nix gemacht. der inhalt soll lediglich gelesen und in das QTextEdit eingefuegt werden.

das ganze einlesen der daten geht fuer ne 250MB grosse textdatei recht fix (bei meinem rechner ca. 2 sek.) --> aber wie nun diese daten in das QTextEdit (oder was auch immer fuer ein Text-Widget das highlighting unterstuetz, weil es sich bei besagtem file um logfile handelt und diverse text-passage spaeter gehighlighted werden sollen).

ich denke auch dass die meiste zeit fuers einfuegen drauf gehen wird, aber da tun sich dann schon die naechsten fragen auf:

wo und wie fuege ich es ein ? --> laeuft wieder auf threads hinaus und thread-uebergreifende gui-operationen sind verboten.

wie macht das eigentlich notepad++ !?!? wenn ich da eine 500MB (!!) grosse Textdatei aufmache ist der content auf anhieb da :?:
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Beitrag von Christian81 »

Notepad liest immer nur einen Teil der Datei. Das solltest Du auch machen - alles andere kann nicht gehen (wie auch...)
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
nisa06
Beiträge: 11
Registriert: 22. Mai 2008 11:10

Beitrag von nisa06 »

ok, noch eine frage:

falls mein file-loading in nem eigenen thread laeuft undzwar so (steht in meinem main-gui-code):

Code: Alles auswählen

fileloader.moveToThread(workerThread);
QObject::connect(workerThread,  SIGNAL(started()),  &fileloader,  SLOT(ReadContent()));
workerThread->start();
wie schaffe ich es dann innerhalb von meinem fileloader bestimmte anzahl von ms zu warten !?!? thread()->msleep(..) geht nicht da protected.
ich moechte jedoch innerhalb von ReadContent nach dem emiten eines signals bestimmte anzahl von millisekunden warten.
wie mache ich das !?!
Antworten