Запуск кода в основном цикле

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

Я делаю это неправильно? Является ли Qt принудительным выполнением пользовательского кода в потоках, если мы хотим запустить что-то в цикле?

3 ответа

Решение
QTimer::singleShot(0, []{/* your code here */});

Вот и все, правда. Использование таймера 0 мс означает, что ваш код будет выполняться на следующей итерации цикла событий. Если вы хотите убедиться, что код не будет работать, если определенный объект больше не существует, предоставьте объект контекста:

QTimer::singleShot(0, contextObj, []{/* your code here */});

Это хорошо задокументировано.

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

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

auto timer = new QTimer(parent);
connect(timer, &QTimer::timeout, contextObj, []{/* your code here */});
timer->start();

(Примечание: интервал по умолчанию равен 0 мс, если вы не установили его, поэтому QTimer::timeout() генерируется каждый раз, когда события заканчивают обработку.)

Вот где это поведение задокументировано.

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

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

class EvtLoopTicker : public QObject
{
    Q_OBJECT
public:

    EvtLoopTicker(QObject *pParent = nullptr)
        : QObject(pParent)
    {}

public slots:

    void launch()
    {
        tickNext();
    }

private slots:

    void tick()
    {
        qDebug() << "tick";

        // Continue ticking
        tickNext();
    }

private:

    void tickNext()
    {
        // Trigger the tick() invokation when the event loop runs next time
        QMetaObject::invokeMethod(this, "tick", Qt::QueuedConnection);
    }

};

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

    EvtLoopTicker ticker;
    ticker.launch();

    return a.exec();
}

Еще один способ был бы переопределить:

bool QCoreApplication::event(QEvent *e)

зарегистрировать пользователя QEvent и опубликуйте мероприятие в QCoreApplicatio::instance(), Очевидно, что QTimer подход превосходен, но этот будет работать, даже если исходящий поток не был создан Qt (QThread).

пример:

class MainThreadEvent: public QEvent
{
  std::function<void()> f_;

public:
  template <typename F>
  explicit MainThreadEvent(F&& f) :
    QEvent(event_type()),
    f_(std::forward<F>(f))
  {
  }

  void invoke()
  {
    f_();
  }

  static auto event_type()
  {
    static int et{-1};

    return QEvent::Type(-1 == et ? et = registerEventType() : et);
  }

  template <typename F>
  static void post(F&& f)
  {
    auto const app(QCoreApplication::instance());

    app->postEvent(app, new MainThreadEvent(std::forward<F>(f)));
  }
};

class UserApplication: public QApplication
{
  using QApplication::QApplication;

  bool event(QEvent* const e) final
  {
    if (MainThreadEvent::event_type() == e->type())
    {
      return static_cast<MainThreadEvent*>(e)->invoke(), true;
    }
    else
    {
      return QApplication::event(e);
    }
  }
};
Другие вопросы по тегам