LINUX.ORG.RU

Qt условная отправка сигнала по таймеру

 ,


0

1

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

class Worker : public QObject
{
    Q_OBJECT
public:
    // ...
    bool isWorking();

signals:
    void resultReady(int);

public slots:
    void doWork() {
        {
          QMutexLocker locker(&isWorkingLock_);
          isWorking_ = true;
        }
        // ...
        emit resulReady(res);
        {
          QMutexLocker locker(&isWorkingLock_);
          isWorking_ = false;
        }
    }

private:
    bool isWorking_ {false};
    QMutex isWorkingLock_;
};

worker_ типа Worker прикреплен к потоку и запускает работу doWork() по таймеру:

    workerThread_.setPriority(QThread::LowestPriority);
    worker_->moveToThread(&workerThread_);
    connect(workerTimer_, &QTimer::timeout, worker_, &Worker::doWork);
Остановка worker_ выполняется путем остановки таймера. Но, после выключения таймера некоторое время dT работа doWork() продолжается (похоже, что извлекаются сигналы, накопленные в events queue).

Как правильно снизить время dT до нуля:

1. сигнал timeout испускать при условии, что worker_.isWorking() вернул false?

вот так пробовал

    connect(workerTimer_, &QTimer::timeout, worker_, 
            [&]()
            {
                if (!worker_->isWorking()) {
                    qDebug() << "emit";
                    emit worker_->makeFrames();
                }
                else
                {
                    qDebug() << "not emit";

                }
            }
            );
но, в консоле всегда emit, и при этом проблема не решается: после остановки таймера работа doWork() повторяется некоторое время dT.

2. очистить events queue? Как правильно?

3. что-то еще? Может я неправильно понимаю ситуацию ...



Последнее исправление: cppprogger (всего исправлений: 3)

Но, после выключения таймера некоторое время dT работа doWork() продолжается

как ты гарантируешь, что doWork успевает отработать до следующего выстрела таймера?

aol ★★★★★
()
Ответ на: комментарий от aol

Но, после выключения таймера некоторое время dT работа doWork() продолжается

как ты гарантируешь, что doWork успевает отработать до следующего выстрела таймера?

Я не точно выразился. doWork() после выключения таймера отрабатывает не один раз, а некоторое количество раз — от 5-ти до 20-ти раз.

Т.е. не страшно, что когда я отключил таймер doWork() завершиться. Проблема в том, что doWork() почему-то еще запускается порядочное количество раз.

cppprogger
() автор топика
Ответ на: комментарий от cppprogger

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

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

такие таймеры(в разных подобных системах) обычно просто бросают ивенты в системную очередь, которые потом раздаются тем, кому предназначены. то есть накапливаются.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)

Остановка worker_ выполняется путем остановки таймера.

Это как?

PS: не проще ли использовать atomic bool?

RazrFalcon ★★★★★
()
Ответ на: комментарий от RazrFalcon

PS: не проще ли использовать atomic bool?

QMutexLocker locker(&isWorkingLock_);
isWorking_ = true;

это не к месту. бул итак атомик. но для красоты можно писать atomic::bool, что и будет просто бул.

alysnix ★★★
()
Ответ на: комментарий от alysnix

бул итак атомик. но для красоты можно писать atomic::bool, что и будет просто бул

Ложное высказывание. Про memory barrier слышал?

Lzzz
()
Ответ на: комментарий от Lzzz

Про memory barrier слышал?

слышал. а где он тут нужен? один тред пишет в бул, другой читает. они никак не синхронизованы, и чтение идет - как попадет. единственная польза от атомика тут будет та, что вроде можно тогда не обьявлять volatile. а вот volatile там надо ставить обязательно.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

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

не работал с кутэ, так и не советуй ерунду

x905 ★★★★★
()
Ответ на: комментарий от peregrine

Лол, нет. В C++ вообще нет ни одного атомарного типа, если явно не указать std::atomic…

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

alysnix ★★★
()
Ответ на: комментарий от alysnix

ему уже сказали - оформить пример на github

x905 ★★★★★
()
  1. таймер разумеется должен кидать ивенты если isWorking()==false

  2. проверить интервал таймера. может он слишком частый и успевает накидать ивентов в промежутке пока isWorking == false.

  3. посмотреть не мог ли накидать таймер ивентов при старте программы.

  4. можно сделать oneShotTimer, и его будет пускать функция doWork в последней строчке. но тут интервал запусков будет не особо точный, если необходим достаточно точный, то так делать не стоит.

alysnix ★★★
()
Ответ на: комментарий от peregrine

Я как бы намекаю на стандарт.

а в стандарте написано, где инстанцируется таблица виртуальных методов класса, в каком обьектном файле ее искать?

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от aol

Минимальный пример. После остановки таймера timeout() испускается десяток раз.

P.S. В main с opencv не приципиально, просто с ходу так по пробелу таймер тормознуть пришло в голову )))

Worker


class Worker : public QObject
{
    Q_OBJECT
public:
    explicit Worker(QObject *parent = nullptr);
    bool isWorking();

signals:
    void resultReady(int);

public slots:
    void makeResult();

private:
    bool isWorking_ {false};
    QMutex isWorkingLock_;
};


Worker::Worker(QObject *parent)
    : QObject(parent)
{
}


bool Worker::isWorking()
{
    QMutexLocker locker(&isWorkingLock_);
    return isWorking_;
}


void Worker::makeResult()
{
    qDebug() << "Worker::makeResult";
    {
        QMutexLocker locker(&isWorkingLock_);
        isWorking_ = true;
    }

    QThread::usleep(10000);

    emit resultReady(555);

    {
        QMutexLocker locker(&isWorkingLock_);
        isWorking_ = false;
    }
}

Controller

class Controller : public QObject
{
    Q_OBJECT
public:
    explicit Controller(QObject *parent = nullptr);
    ~Controller();

public slots:
    void startWork();
    void stopWork();
    void setResult(int);
//    void test();

private:
    QThread workerThread_;
    Worker *workerWorker_;
    QTimer *workerTimer_;
};


void Controller::startWork() {
    qDebug() << "start";
    workerTimer_->start(10);
    qDebug() << "timer_->isActive = " << workerTimer_->isActive();
}


void Controller::stopWork() {
    qDebug() << "stop";
    workerTimer_->stop();
    qDebug() << "timer_->isActive = " << workerTimer_->isActive();
}


void Controller::setResult(int i)
{
    qDebug() << i;
}


//void Controller::test()
//{
//    qDebug() << "Controller::test";
//}


Controller::Controller(QObject *parent) : QObject(parent)
{
    qDebug() << "Controller::Controller";
    workerTimer_ = new QTimer(this);
    workerTimer_->setSingleShot(false);


    workerWorker_ = new Worker;
    workerWorker_->moveToThread(&workerThread_);
    workerThread_.start();
    qDebug() << "workerThread_.isRunning() = " << workerThread_.isRunning();

 //   connect(workerTimer_, &QTimer::timeout, this,  &Controller::test);
    connect(workerTimer_, &QTimer::timeout, workerWorker_, //&Worker::makeResult);
            [&]()
            {
                if (!workerWorker_->isWorking()) {
                    qDebug() << "emit: " << " isActive = " << workerTimer_->isActive();
                    emit workerWorker_->makeResult();
                }
                else
                {
                    qDebug() << "not emit";

                }
            }
            );
   connect(workerWorker_, &Worker::resultReady, this, &Controller::setResult);
   connect(&workerThread_, &QThread::finished, workerWorker_, &QObject::deleteLater);
}


Controller::~Controller()
{
    qDebug() << "Controller::~Controller";
    workerThread_.quit();
    workerThread_.wait();
}

main

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    cv::namedWindow("TEST", cv::WINDOW_AUTOSIZE);

    Controller controller;
    controller.startWork();
    while(true) {
        auto key = cv::waitKey(1);
        if(key != -1) {
            qDebug() << "MAIN STOP";
            controller.stopWork();
            break;
        }
    }
    return a.exec();
}

cppprogger
() автор топика
Ответ на: комментарий от cppprogger

После остановки таймера timeout() испускается десяток раз

Это они уже лежат в очереди, и остановка таймера не влияет. Не особо разбиралась с Qt, но если таймер запустить на отдельной нити, то евенты будут попадать в очередь или доставляться напрямую в слот?

Lzzz
()
Ответ на: комментарий от Lzzz

Ну я тоже в начале топика такое предположение высказывал. Но, не понятно, как очистить events queue, который внутри workerQThread_.

cppprogger
() автор топика
Ответ на: комментарий от cppprogger

Я бы просто вызывала this_thread::sleep_for() внутри нити воркера.

Lzzz
()
Ответ на: комментарий от cppprogger

в твоем минимальном примере до запуска event loop дойдет только после нажатия на кнопку. а там уж выходить пора.

плохой, негодный пример….

P.S.: внезапно, за запуск event loop-а отвечает a.exec()

aol ★★★★★
()
Последнее исправление: aol (всего исправлений: 1)
Ответ на: комментарий от aol

в твоем минимальном примере до запуска event loop дойдет только после нажатия на кнопку P.S.: внезапно, за запуск event loop-а отвечает a.exec()

В каждом из потоков main-thread и в workerThread свои EvantLooop и EventsQueue.

Дочерний поток запускается в конструкторе контроллера. Кнопка пробел используется для остановки потока.

плохой, негодный пример….

покажи годный

cppprogger
() автор топика
Ответ на: комментарий от cppprogger

Повторяю, в твоём минимальном примере event loop основного потока стартует непосредственно перед выходом из программы. Не надо так.

покажи годный

На основе полученной информации, ты и сам можешь доработать его. Мне сегодня с телефона не удобно программировать. Будет время завтра на днях - покажу, если сам не родишь к тому времени.

aol ★★★★★
()
Ответ на: комментарий от alysnix

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

вообще, он может разные значения в кеши разных ядер записать

next_time ★★★★★
()
Ответ на: комментарий от aol

Будет время завтра на днях - покажу, если сам не родишь к тому времени.

Спасибо. В любом случае будет очень интересно посмотреть.

cppprogger
() автор топика
Ответ на: комментарий от next_time

Чего-то я не понимаю :-( . Запустил QCoreApplication в отдельном классе. А ГУЙ сделал средствами OpenCV. Ловлю

QObject::startTimer: Timers can only be used with threads started with QThread

class Library: public QObject
{
Q_OBJECT
public:
    Library();
private slots:
    void onStarted();
private:
    QCoreApplication *app = NULL;
    QThread *thread = NULL;
};


Library::Library()
{
    thread = new QThread;
    connect(thread, &QThread::started, this, &Library::onStarted,  Qt::DirectConnection);
    thread->start();
}

void Library::onStarted()
{
    int argc = 1;
    char* argv[] = {"Library", NULL};
    app = new QCoreApplication(argc, argv);
    app->exec();
}

В main запускаю

int main(int argc, char *argv[])
{
    Library library;
    Controller controller(&library);
    controller.startWork();
    controller.stopWork();
// далее OpenCV - шное окно 

cppprogger
() автор топика
Ответ на: комментарий от cppprogger

Qt::QueuedConnection вместо Qt::DirectConnection не помогает? кроме того, не вижу в вашем коде moveToThread, применённого к Library

next_time ★★★★★
()
Ответ на: комментарий от next_time

не получается

QObject::startTimer: Timers can only be used with threads started with QThread
WARNING: QApplication was not created in the main() thread.

Пытаюсь сунуть только QCoreApplication в отдельный класс:

class Library: public QObject
{
Q_OBJECT
public:
    Library();
public slots:
    void onStarted();
private:
    QCoreApplication *app = NULL;
};


Library::Library()
{
}

void Library::onStarted()
{
    int argc = 1;
    char* argv[] = {"Library", NULL};
    app = new QCoreApplication(argc, argv);
    app->exec();
}

А в основном коде прикрепляем его к потоку

int main(int argc, char *argv[])
{
    Library library;
    QThread *thread = new QThread;
    QObject::connect(thread, &QThread::started, &library, &Library::onStarted);
    thread->start();
    library.moveToThread(thread);

cppprogger
() автор топика
Ответ на: комментарий от next_time

те же ошибки ... что-то здесь принципиально не так ... но с Qt::DirectConnection похоже хотябы поток и QCoreApplication создает.

int main(int argc, char *argv[])
{
    Library library;
    QThread *thread = new QThread;
    thread->start();
    library.moveToThread(thread);

    QObject::connect(thread, &QThread::started, &library, &Library::onStarted, Qt::DirectConnection);

//QObject::startTimer: Timers can only be used with threads started with QThread
//WARNING: QApplication was not created in the main() thread.

или

  QObject::connect(thread, &QThread::started, &library, &Library::onStarted, Qt::QueuedConnection);
// QObject::startTimer: Timers can only be used with threads started with QThread
//QEventLoop: Cannot be used without QApplication

cppprogger
() автор топика
Ответ на: комментарий от cppprogger

А зачем создавать QCoreApplication внутри QThread? У последнего же свой event-loop.

xaizek ★★★★★
()

Google Stackoverflow не дал результата?

bhfq ★★★★★
()
Ответ на: комментарий от cppprogger

Спасибо. В любом случае будет очень интересно посмотреть.

кушайте, не обляпайтесь :)

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

Если ты не знаешь, сколько будет выполняться очередная задача, но следующую тебе надо запустить только после очередного таймаута таймера, то можно сделать, например, вот так:

на сигнал QTimer::timeOut повесь отдельный слот в MainWindow, в котором испускай сигнал, например, enqueue и сразу делай workerTimer->stop(). enqueue соедини с Worker::makeResult

В слоте, принимающем инфу от Worker::resultReady делай workerTimer->start()

или, что еще проще, использовать, в таком случае, таймер в singleShot режиме.

aol ★★★★★
()
Последнее исправление: aol (всего исправлений: 1)
Ответ на: комментарий от aol

Спасибо! Явление работы таймера после его остановки отлично видно. В основном приложении все-таки решил испускать один выстрел таймера, т.к. строгое соблюдение интервала не критично.

cppprogger
() автор топика
Ответ на: комментарий от next_time

ещё здесь ошибка, т.к. QCoreApplication при этом отправляется в один поток с Library

Qt говорит о Warning, а я как бы целеноправленно сделал Library для этого.

А почему это критично - держать QCoreApplication в главном потоке? A если я хочу сделать shared library на базе QCoreApplication и использовать его в не только Qt приложении?

cppprogger
() автор топика
Ответ на: комментарий от cppprogger

Видимо потому, что очередь событий должна быть в одном потоке с QCoreApplication.

A если я хочу сделать shared library на базе QCoreApplication и использовать его в не только Qt приложении?

не знаю, слышал про такую возможность, но ни разу не использовал. думаю, всё будет нормально.

next_time ★★★★★
()
Ответ на: комментарий от cppprogger

а я как бы целеноправленно сделал Library для этого.

но зачем? я бы попробовал QCoreApplication запихнуть в std::thread в таком случае - т.к. std::thread ничего не знает об очереди событий qt должно сработать

next_time ★★★★★
()
Последнее исправление: next_time (всего исправлений: 1)

Всем спасибо за ответы. В итоге, тема закрыта.

Чтобы не накапливать события от таймера в очереди нужно использовать одиночный выстрел setSingleShot(true).

Побочный вопрос, связанный с потоко-безопастным методом диагностики состояния объекта bool isWorking(): конечно же std::atomic<bool> более эффективен чем мьютексы в данном случае.

cppprogger
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.