Потребовалось в одном проекте в критическом к скорости выполнения месте дёргать функции сторонней библиотеки, но загвоздка в том, что эти функции внутри ещё и дожидаются результата в цикле/магия. Мне от неё важно только получить код ошибки (если сразу возник), другой результат не так важен (потом запрашивается отдельно).

В общем задача: запустить функцию, по возможности дождаться результат, но не дольше N, если не удалось (время вышло), поставить флаг, что не удалось, что бы потом запросить отдельно.

Можно было бы вообще всё использование вынести в отдельный поток, но городить ради одного места неохото, зато родились вот такие извращения:

  1. В Qt есть QtConcurrent, с помощью которого можно запустить функцию в отдельном потоке,
  2. Так как нам желательно сразу получить результат не выходя из функции (в случае ошибки вызываемая функция особо не задерживается), то можно прописать future.result(), но тогда мы будем ждать до победного, а нам нужно ограничить время выполнения, поэтому
  3. Используем QTimer, что бы по истечению заданного времени завершить выполнение, но и тут облом — мы не можем принудительно завершить выполнение «QtConcurrent::run», (т.к. вызываемая функция может не освободить память/не закрыть доступ к файлу да и вообще хз что). Поэтому завершать выполнение не будем, а тупо забьём на него — пусть там себе в параллельном потоке выполняется, а мы тем временем пойдём дальше, но тогда будет необходимо использовать сигналы, что бы дать понять, когда пора выходить из функции по истечению времени, а когда функция вернула результат, поэтому
  4. Используем QFutureWatcher, что бы подписаться на сигнал «finished» если функция выполнилась.
  5. Ну а что бы ждать сигнала от таймера, либо сигнала о завершении вызываемой функции и не выходить до этого из текущей функции используем  QEventLoop.  Как только тот или другой сигнал будет вызван и обработан, то завершаем цикл.

Из преимуществ: выполнение происходит в отдельном потоке, поэтому не затрагиваем основной процесс, в котором происходит вызов функции.

Из недостатков: мы не управляем параллельным процессом в полной мере, т.е. не можем принудительно завершить процесс и он будет болтаться в памяти до исключения, завершения или посинения (если функция «зациклится»).

В общем, вот так:

#include <QtConcurrent/QtConcurrentRun>
#include <QFuture>
#include <QFutureWatcher>
#include <QEventLoop>
#include <QTimer>
#include <QDebug>

....

void AppCore::runAsync()
{
    qDebug()<<"Run async";

    QString r;
    QFuture<QString> f;

    try {
        f = QtConcurrent::run(this, &AppCore::asyncFunction);
    } catch(...) {
        qDebug()<<"ERROR";
    }

    QFutureWatcher<QString> w;
    QEventLoop l;
    QTimer t;

    connect(&w, &QFutureWatcher<QString>::finished, [&l, &r, &w](){
        r = w.result();
        qDebug()<<"From connection 1.";
        l.quit();
    });

    connect(&t, &QTimer::timeout, [&l, &r](){
        r = "TIME OUT!";
        qDebug()<<"From connection 2.";
        l.quit();
    });

    w.setFuture(f);
    t.start(2000);
    l.exec();

    qDebug()<<"RESULT:"<<r;

}

/**
* @brief Функция, которая будет выполнятся в фоновом потоке
* @return
*/
QString AppCore::asyncFunction()
{
    qDebug()<<"In async function.";
    QThread::sleep(5);

    qDebug()<<"ASYNC FUNCTION COMPLETED!";

    return QString("COMPLETED NORMAL!");
}

Вывод:

Run async
In async function.
From connection 2.
RESULT: "TIME OUT!"
ASYNC FUNCTION COMPLETED!

Т.е. вышло время, мы получили результат, что время вышла, но сама вызываемая функция в параллельном потоке всё равно выполнилась до конца.

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

/**
* Выполняем функцию в параллельном потоке и отдаём результат,
* если не выполнится за время timeOut_ms, то отдаём timeOut_result
* QtConcurrentRunTimeOut(int, myIntRes, this, &MyClass::myFunction, 2000, -100);
* QtConcurrentRunTimeOut(int, myIntRes, this, &MyClass::myFunctionWithParams, 2000, -100,  14, 88);
* qDebug()<<"RESULT:"<<myIntRes;
* Need include
* #include <QtConcurrent/QtConcurrentRun>
* #include <QFuture>
* #include <QFutureWatcher>
* #include <QEventLoop>
* #include <QTimer>
*/
#define QtConcurrentRunTimeOut(resultType, res, object, functionPointer, timeOut_ms, timeOut_result, ...)\
    resultType _r##res;\
    QFuture<resultType> _f##res;\
    _f##res = QtConcurrent::run(object, functionPointer,##__VA_ARGS__);\
    QFutureWatcher<resultType> _w##res;\
    QEventLoop _l##res;\
    QTimer _t##res;\
    connect(&_w##res, &QFutureWatcher<resultType>::finished, [&_l##res, &_r##res, &_w##res](){\
        _r##res = _w##res.result();\
        _l##res.quit();\
    });\
    connect(&_t##res, &QTimer::timeout, [&_l##res, &_r##res](){\
        _r##res = timeOut_result;\
        _l##res.quit();\
    });\
    _w##res.setFuture(_f##res);\
    _t##res.start(timeOut_ms);\
    _l##res.exec();\
    resultType res=_r##res;

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

Вот как-то так =) 

Leave a Comment

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.

You may also like