Qt: Как отложить выполнение программы, когда QTimer активен? - PullRequest
1 голос
/ 29 января 2011

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

Ссылка: Qt: Как использовать QTimer для печатисообщение для QTextBrowser каждые 10 секунд?

В публикации по приведенной выше ссылке я упростил задачу, которую я пытаюсь сделать, просто сказав, что я хочу нажать кнопку и заставить ее что-то отображать вQTextBrowser каждые 10 секунд.У меня были проблемы с тем, чтобы QTimer работал в то время, поэтому я подумал, что, если я смогу заставить QTimer работать, я мог бы закончить свою задачу.

Что я действительно пытаюсь сделать, это прочитать строки изфайл и после каждых 2500 строк я хотел бы напечатать сообщение и затем подождать 10 секунд.

Псевдокод:

while(not at the end of the file)
{
   read in 2500 lines 

   print a message

   wait 10 seconds
}

QTimer это хорошо, но это противоположно тому, что я хочусделать.Вместо того, чтобы передавать сообщение и ждать 10 секунд, сначала они ждут 10 секунд, тайм-аут, а затем отправляют сообщение.

Итак, чтобы заставить его работать так, как я хочу, я сначала позвонил в printMessage() SLOT, а затем ясделал слот под названием stopTimer(), который просто останавливает таймер.Поэтому по истечении 10 секунд он просто вызовет stopTimer () и затем продолжит обработку входного файла.

В отношении проблемы REAL:

Qt не ожидает завершения QTimerпрежде чем он движется по коду.Я хочу, чтобы код ожидал полных 10 секунд, прежде чем он прочитает следующие 2500 строк кода.Я обнаружил, что QTimer имеет функцию isActive(), которая возвращает значение bool.

Поэтому в то место, где я хотел, чтобы 10-секундная задержка закончилась, я поставил следующее:

while(timer->isActive());

Я полагал, что программа будет оставаться в этом цикле в течение 10 секунд, а затем выйдет послеQTimer истекло время ожидания, потому что тогда условие будет ложным.Проблема в том, что он не выходит из цикла, потому что состояние таймера никогда не меняется независимо от того, как долго он ожидает в этом цикле!Я проверил с помощью отладчика, и isActive( ) остается верным независимо от истекшего времени.

Затем я пропустил цикл while(timer->isActive()) и наблюдал таймер в отладчике.Кажется, что таймер фактически не начинает отсчет времени, пока он не завершит какое-то время (не в конце файла).Поэтому я считаю, что поскольку цикл while(timer->isActive()) вложен в него, он никогда не прерывается.Я могу ошибаться, но похоже, что это происходит.Кроме того, раздражает то, что у объекта QTimer нет поля, которое показывает истекшее время таймера, когда он активен.Поэтому я не могу проверить истекшее время для дальнейшей отладки.

Кто-то, пожалуйста, проверьте это или сообщите мне обходной путь для этого!

Для чего-то, что звучит так просто, это былосамая большая боль, которую я испытывал в последнее время, но я обычно не использую Qt, так что это может быть моим недостатком опыта.

Вот выдержка из кода, который у меня есть, который в настоящее время зависает, как указано выше:

void Form::startBtn_pushed()
{
    QTimer *timer = new QTimer(this);
    QFile file(“file.txt”);
    QTextStream stream(&file);
    QString line;
    int lineCount = 0;

    connect(timer, SIGNAL(timeout()), this, SLOT(stopTimer()));

    while(!(stream.atEnd())
    {
       line = stream.readLine();
       lineCount++;

       if(lineCount == 2500)
       { 
          printMessage();

          timer->start(10000);

          while(timer->isActive()); //Wait for timer to timeout before continuing
       }
    }
}

Извините за длинный пост, и если бы я мог допустить грамматические ошибки с моим кодом здесь.У меня нет доступа в интернет на моей машине разработчика, поэтому мне пришлось перепечатать это здесь.

Ответы [ 5 ]

7 голосов
/ 29 января 2011

Делать что-то вроде while(timer.isActive()) не очень хорошая идея, так как это заставит ваше приложение потреблять около 100% процессорного времени. Это также приведет к тому, что ваше приложение никогда не вернется в цикл обработки событий, где выполняется фактический код таймера, поэтому он зависает.

Если вы все еще хотите использовать этот подход, вам следует вызвать QCoreApplication :: processEvents () в цикле. Он временно передает управление обратно в цикл обработки событий, поэтому таймер истекает. Вместо того, чтобы подключать timeout () к stopTimer (), вы можете просто вызвать timer.setSingleShot (true) перед его запуском, это приведет к его автоматической остановке после первого тайм-аута.

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

Если вы хотите более элегантный подход, вы можете создать отдельный класс для чтения этого файла. В конструкторе вы открываете свой файл и поток, которые должны быть полями в этом классе. Этот класс также должен иметь своего рода слот readMore (), который будет читать 2500 строк, затем помещать сообщение и возвращаться. Если он не достигнет конца потока, он вызовет QTimer :: singleShot (10000, this, SLOT (readMore ())), что заставит цикл обработки событий снова вызвать readMore () через 10 секунд. Код будет выглядеть примерно так (не проверять на ошибки):

// myfilereader.h

class Form;

class MyFileReader: public QObject {
Q_OBJECT
public:
  MyFileReader(const QString &fileName);
  // this should be called after you create an instance of MyFileReader
  void startReading() {readMore();}
private:
  QFile file;
  QTextStream stream;
private slots:
  void readMore();
signals:
  void message(); // this should be connected to printMessage() in the Form
  void finished();
};

// myfilereader.cpp

MyFileReader::MyFileReader(const QString &fileName):
  file(fileName),
  stream(&file),
{
  // open the file, possibly throwing an exception
  // or setting some sort of "invalid" flag on failure
}

void MyFileReader::readMore()
{
    QString line;
    int lineCount = 0;

    while(!(stream.atEnd())
    {
       line = stream.readLine();
       lineCount++;

       if(lineCount == 2500)
       { 
          emit message();
          break;
       }
    }
    if (stream.atEnd())
       emit finished();
    else
       QTimer::singleShot(10000, this, SLOT(readMore()));
}

Это своего рода более тяжелый подход, но это цена асинхронной обработки событий. Вы также можете поместить все эти вещи в класс Form, но я думаю, что лучше использовать отдельный класс.

Как указывает Даниэль, если чтение 2500 строк занимает много времени, например, 5 секунд, сообщение будет напечатано через 10 секунд после окончания чтения, то есть через 15 секунд после его начала. Если вы хотите, чтобы сообщение печаталось примерно каждые 10 секунд, независимо от того, сколько времени занимает чтение, вы должны добавить поле QTimer timer в класс, подключить его сигнал timeout () к слоту readMore () в конструкторе MyFileReader, а затем в вызов метода startReading () timer.start () перед вызовом readMore (). Теперь в конце readMore () сделайте это:

    if (stream.atEnd()) {
       timer.stop();
       emit finished();
    }

В этом случае вам нужно поле QTimer, потому что вы не можете отменить вызов QTimer :: singleShot (), но вам нужно сделать это, если вы достигли конца потока, иначе ваш readMore () просто сохранит звонить снова и снова, даже если больше нечего читать. Обратите внимание, что даже в этом случае сообщение может появляться реже, чем каждые 10 секунд, если чтение 2500 строк занимает больше времени, чем эти 10 секунд. Если вы хотите ровно 10 секунд, вам, вероятно, следует вместо этого проверить истекшее время в цикле, но я думаю, что это излишнее, если только вы не ожидаете, что чтение будет очень медленным.

Немного не по теме, но если вы хотите простой способ избежать утечек памяти, вы также можете сделать это в конструкторе:

connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));

Он автоматически помечает ваш читатель для удаления, когда вы emit finished(), и, как только это произойдет, читатель будет удален, как только элемент управления вернется в цикл обработки событий. То есть после того, как все слоты, связанные с завершенным (), возвращают сигнал. Этот подход позволяет вам просто выделить MyFileReader в куче, а затем отбросить указатель, не беспокоясь об утечках памяти.

1 голос
/ 29 января 2011

QTimer имеет сигнал timedout (). Если вы подключите к этому слот, вы можете установить начальный таймер на ДЕЙСТВИТЕЛЬНО короткий интервал (возможно, 1 мс). Когда таймер истекает, внутри слота вы можете отправить свое сообщение. В конце слота установите интервал в 10 секунд, установите для одиночного снимка значение false и снова запустите таймер.

Если вы не хотите выполнять настройку каждый раз при вызове слота, вы можете просто сделать 2 слота. В первый раз слот выполняет настройку для остальных вызовов. Он также отключается от QTimer и подключает второй слот. Тогда дела продолжатся весело.

Редактировать:

Также следует понимать, что при вызове слотов они вызываются в потоке событий. Таким образом, нажав кнопку и поместив цикл, в котором спин-блокирует / занят-ждет до истечения таймера, вы гарантируете, что не войдете в это состояние, поскольку блокируете тот самый поток, в котором будет обрабатываться сигнал тайм-аута.

0 голосов
/ 27 февраля 2013
QEventLoop l;
connect( timer, SIGNAL( timeout() ), &l, SLOT( quit() ) );
l.exec(); // Waiting without freezing ui
0 голосов
/ 02 февраля 2011

Я нашел решение, которое намного проще, чем предложенное здесь.

Я понял, что гораздо проще использовать объект QTime вместо объекта QTimer.объект QTime, а затем вы используете его функцию elapsed (), чтобы проверить, сколько времени прошло с момента его запуска.

Спасибо всем, что нашли время помочь.Я пытался внедрить пару ваших решений в мой код, но у меня возникли некоторые проблемы, так как я не профессионал в Qt, и они были более сложными, чем я мог подумать.В конце концов я нахожу это решение простым решением проблемы, которая должна была быть легкой.

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

Еще раз спасибо!

Мое решение:

    void Form::startBtn_pushed()
    {
        QTime *timer = new QTime();
        QFile file(“file.txt”);
        QTextStream stream(&file);
        QString line;
        int lineCount = 0;

        connect(timer, SIGNAL(timeout()), this, SLOT(stopTimer()));

        while(!(stream.atEnd())
        {
           line = stream.readLine();
           lineCount++;

           if(lineCount == 2500)
           { 
              timer->start();

              while(1)
              {
                 QApplication::processEvents();

                 if(timer->elapsed() == 10000)
                 {
                    printMessage();
                    break;
                 }

              }
           }
        }
    }
0 голосов
/ 29 января 2011

QTimer выполняет всю свою обработку в цикле событий Qt, поэтому ваш код должен вернуться в цикл обработки событий, чтобы таймер отключился. Итак, вы хотите, чтобы startBtn_pushed настроил таймер, подключил слот к сигналу timeout таймера, а затем, вероятно, сам вызов этого слота (чтобы он вызывался немедленно). Это будет выглядеть примерно так:

// timer, file, and stream are now instance variables (or maybe file and stream
// are broken out into their own class... up to you.
void Form::startBtn_pushed()
{
     // timer has been allocated before (but not started)

     file.open(“file.txt”);
     stream.setDevice(&file);

     connect(timer, SIGNAL(timeout()), this, SLOT(readAndPrint()));
     timer->start(10000);

     readAndPrint(); // respond to the button press immediately
}

void Form::readAndPrint()
{
    int lineCount = 0;

    while(!(stream.atEnd() && lineCount < 2500)
    {
        line = stream.readLine();
        lineCount++;

        if(lineCount == 2500)
        { 
            printMessage();
        }
    }
}
...