2013-05-25 54 views
2

今天我遇到了一個來自QT庫的repaint()函數的問題。長話短說,我有一個插槽,我使用BP算法訓練我的神經網絡。我已經在控制檯中測試了整個算法,然後想將它移到GUI應用程序中。一切正常,除了刷新。神經網絡的訓練是一個包含大量計算的過程,這些計算是在函數(訓練)和licz_mse函數(計算當前誤差)中進行的。變量ilosc_epok可以設置爲1e10。因此整個過程可能會持續幾個小時。這就是爲什麼我想在每個100000個時代(最後的if聯繫)後顯示當前進度。 wyniki是用於顯示進度的QTextEdit類的一個對象。不幸的是,repaint()不按預期工作。在開始時,它在GUI中刷新wyniki,但經過一段隨機時間後,它停止工作。當外部循環完成時,它會再次刷新顯示所有更改。Refreshing with repaint()停止工作

我試圖改變刷新的頻率,但遲早會停止(除非整個訓練過程由於滿足中斷條件而停止足夠早)。看起來應用程序由於計算過多而決定停止刷新。伊莫它不應該發生。我在尋找老問題的解決方案,並設法解決這個問題,當我使用qApp->processEvents(QEventLoop::ExcludeUserInputEvents);而不是wyniki->repaint();。然而,我仍然很好奇,爲什麼repaint()就這樣停止工作。

下面我粘貼有問題的部分代碼的一部分。我使用QT Creator 2.4.1和QT庫4.8.1如果有幫助。

unsigned long int ile_epok; 
double mse_w_epoce; 
for (ile_epok=0; ile_epok<ilosc_epok; ile_epok++) { //external loop of training 
    mse_w_epoce = 0; 
    for (int i=0; i<zbior_uczacy_rozmiary[0]; i++) { //internal loop of training 
     alg_bp(zbior_uczacy[i], &zbior_uczacy[i][zbior_uczacy_rozmiary[1]]); 
     mse_w_epoce += licz_mse(&zbior_uczacy[i][zbior_uczacy_rozmiary[1]]); 
    } 
    //checking break condition 
    if (mse_w_epoce < warunek_stopu) { 
     wyniki->append("Zakończono uczenie po " + QString::number(ile_epok) + " epokach, osiągając MSE: " + QString::number(mse_w_epoce)); 
     break; 
    } 
    //problematic part 
    if ((ile_epok+1)%(100000) == 0) { 
     wyniki->append("Uczenie w toku, po " + QString::number(ile_epok+1) + " epokach MSE wynosi: " + QString::number(mse_w_epoce)); 
     wyniki->repaint(); 
    } 
} 
+0

您是否嘗試過使用'update',而不是'repaint'?這是[Qt文檔](http://qt-project.org/doc/qt-5.0/qtwidgets/qwidget.html#repaint)推薦的功能。 –

+0

是的,在開始時,update()不適合我的問題,因爲它在循環消失時刷新我的小部件。它不會立即調用paintEvent,這對我的問題是必需的。 – Givi

+1

這是在Gui線程中運行嗎? – ExpatEgghead

回答

0

您正在屏蔽您的GUI線程,因此重繪將無法正常工作,這只是設計不當而已。你是從來沒有應該阻止GUI線程。

如果你堅持要在GUI線程中完成這項工作,你必須強制將工作分成小塊並在每個塊後返回主事件循環。嵌套的事件循環是邪惡,所以不要以爲你會想要一個。所有這些都有不好的代碼味道,所以不要離開。

或者,只需將您的計算QObject移動到工作線程並在那裏完成工作。

下面的代碼演示了這兩種技術。很容易注意到,切割工作需要在工作對象內維持循環狀態,而不僅僅侷限於循環中。它更混亂,代碼味道不好,再次 - 避免它。

該代碼在Qt 4.8和5.1下工作。

Screenshot

//main.cpp 
#include <QApplication> 
#include <QThread> 
#include <QWidget> 
#include <QBasicTimer> 
#include <QElapsedTimer> 
#include <QGridLayout> 
#include <QPlainTextEdit> 
#include <QPushButton> 

class Helper : private QThread { 
public: 
    using QThread::usleep; 
}; 

class Trainer : public QObject { 
    Q_OBJECT 
    Q_PROPERTY(float stopMSE READ stopMSE WRITE setStopMSE) 
    float m_stopMSE; 
    int m_epochCounter; 
    QBasicTimer m_timer; 
    void timerEvent(QTimerEvent * ev); 
public: 
    Trainer(QObject *parent = 0) : QObject(parent), m_stopMSE(1.0) {} 
    Q_SLOT void startTraining() { 
     m_epochCounter = 0; 
     m_timer.start(0, this); 
    } 
    Q_SLOT void moveToGUIThread() { moveToThread(qApp->thread()); } 
    Q_SIGNAL void hasNews(const QString &); 
    float stopMSE() const { return m_stopMSE; } 
    void setStopMSE(float m) { m_stopMSE = m; } 
}; 

void Trainer::timerEvent(QTimerEvent * ev) 
{ 
    const int updateTime = 50; //ms 
    const int maxEpochs = 5000000; 
    if (ev->timerId() != m_timer.timerId()) return; 

    QElapsedTimer t; 
    t.start(); 
    while (1) { 
     // do the work here 
     float currentMSE; 
#if 0 
     for (int i=0; i<zbior_uczacy_rozmiary[0]; i++) { //internal loop of training 
      alg_bp(zbior_uczacy[i], &zbior_uczacy[i][zbior_uczacy_rozmiary[1]]); 
      currentMSE += licz_mse(&zbior_uczacy[i][zbior_uczacy_rozmiary[1]]); 
     } 
#else 
     Helper::usleep(100); // pretend we're busy doing some work 
     currentMSE = 2E4/m_epochCounter; 
#endif 
     // bail out if we're done 
     if (currentMSE <= m_stopMSE || m_epochCounter >= maxEpochs) { 
      QString s = QString::fromUtf8("Zakończono uczenie po %1 epokach, osiągając MSE: %2") 
        .arg(m_epochCounter).arg(currentMSE); 
      emit hasNews(s); 
      m_timer.stop(); 
      break; 
     } 
     // send out periodic updates 
     // Note: QElapsedTimer::elapsed() may be expensive, so we don't call it all the time 
     if ((m_epochCounter % 128) == 1 && t.elapsed() > updateTime) { 
      QString s = QString::fromUtf8("Uczenie w toku, po %1 epokach MSE wynosi: %2") 
         .arg(m_epochCounter).arg(currentMSE); 
      emit hasNews(s); 
      // return to the event loop if we're in the GUI thread 
      if (QThread::currentThread() == qApp->thread()) break; else t.restart(); 
     } 
     m_epochCounter++; 
    } 
} 

class Window : public QWidget { 
    Q_OBJECT 
    QPlainTextEdit *m_log; 
    QThread *m_worker; 
    Trainer *m_trainer; 
    Q_SIGNAL void startTraining(); 
    Q_SLOT void showNews(const QString & s) { m_log->appendPlainText(s); } 
    Q_SLOT void on_startGUI_clicked() { 
     QMetaObject::invokeMethod(m_trainer, "moveToGUIThread"); 
     emit startTraining(); 
    } 
    Q_SLOT void on_startWorker_clicked() { 
     m_trainer->moveToThread(m_worker); 
     emit startTraining(); 
    } 
public: 
    Window(QWidget *parent = 0, Qt::WindowFlags f = 0) : 
     QWidget(parent, f), m_log(new QPlainTextEdit), m_worker(new QThread(this)), m_trainer(new Trainer) 
    { 
     QGridLayout * l = new QGridLayout(this); 
     QPushButton * btn; 
     btn = new QPushButton("Start in GUI Thread"); 
     btn->setObjectName("startGUI"); 
     l->addWidget(btn, 0, 0, 1, 1); 
     btn = new QPushButton("Start in Worker Thread"); 
     btn->setObjectName("startWorker"); 
     l->addWidget(btn, 0, 1, 1, 1); 
     l->addWidget(m_log, 1, 0, 1, 2); 
     connect(m_trainer, SIGNAL(hasNews(QString)), SLOT(showNews(QString))); 
     m_trainer->connect(this, SIGNAL(startTraining()), SLOT(startTraining())); 
     m_worker->start(); 
     QMetaObject::connectSlotsByName(this); 
    } 
    ~Window() { 
     m_worker->quit(); 
     m_worker->wait(); 
     delete m_trainer; 
    } 
}; 

int main(int argc, char *argv[]) 
{ 
    QApplication a(argc, argv); 
    Window w; 
    w.show(); 
    return a.exec(); 
} 

#include "main.moc"