LINUX.ORG.RU

GUI не перерисовывается в процессе выполнения цикла

 , ,


0

1

Есть список серверов. В таблице. Прохожу циклом по таблице и последовательно коннекчусь к каждому серверу. После коннекта проверяю, есть интернет или нет (curl). Если курл вернул ip адрес, то в соответствующую строку таблицы добавляю этот ip. Код работает, выхлоп qDebug в консольку идет, и в таблицу тоже добавляется, но таблица перерисовывается только когда цикл закончится. В списке порядка 100 локаций, и пройтись по всем занимает около 10 минут. Всё это время окно программы не реагирует ни на что. Как его разморозить, чтобы изменения в таблице отображались в процессе прохождения цикла, а не после его окончания?

Пробовал в отдельном QThread - не помогает,

QStringList vArguments;
    vArguments << "connect.sh";
    QStringList curlArguments;
    curlArguments << "--connect-timeout" << "5" << "ifconfig.me" ;

    QString stdout, ipAddr;
    QBrush brush;
    brush = QBrush(Qt::green, Qt::SolidPattern);
    QRegularExpression ipRegex(R"((\d{1,3}\.){3}\d{1,3})");
    QTableWidgetItem* item;

    bCancelSearch = false;

    for ( int i=0; i<table->rowCount()-1; i++ ){
        //поочередно коннект к серверам через скрипт connect.sh
        QTableWidgetItem *item = table->item(i,0);
        qDebug() << item->text();
        vArguments << item->text();
        vProcess = new QProcess();
        vProcess->setProcessChannelMode(QProcess::MergedChannels);
        vProcess->start( "sudo", vArguments );
        vProcess->waitForFinished();
        vArguments.removeLast();

        //после коннекта проверка наличия инета : curl ifconfig.me
        curlProcess = new QProcess();
        curlProcess->setProcessChannelMode(QProcess::MergedChannels);
        curlProcess->start( "curl", curlArguments );
        curlProcess->waitForFinished();

        stdout = curlProcess->readAllStandardOutput();

        //если curl вернул ip - добаляю его в таблицу
        item = table->item(i,2);
        if ( ipRegex.match(stdout).hasMatch() ){
            int lastPos = stdout.lastIndexOf(QChar('\n'));
            ipAddr = stdout.mid(lastPos+1);
            qDebug() << item->text() << "responded" << ipAddr;
            txtOutput->append(item->text() + " has responded from " + ipAddr);
            item = new QTableWidgetItem();
            item->setText(ipAddr);
            table->setItem(i,4,item);
            for (int j=0; j<5; j++){
                item = table->item(i,j);
                item->setBackground(brush);
            }
            }else{
                qDebug() << "no match" << item->text();
                txtOutput->append(item->text() + " no response");
            }
            if ( bCancelSearch )return;
    }

Как разморозить гуй?

★★★★★

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

Не делать опрос в основном треде и использовать сигналы для обработки изменения состояния.

u-235
()
Ответ на: комментарий от u-235

Пробовал сигнал QProcess::readyReadStandardOutput() коннектить к слоту и в слоте читать выхлоп curl - ничего не дает, всё также замерзший gui.

Та же песня с QThread - переопределил метод run() и в него вставил цикл - пока цикл не закончится gui не перерисовывается.

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

пока цикл не закончится gui не перерисовывается.

В этом вся суть Qt. Пока есть любые события ГУИ не перерисовывается, поэтому ничего маломальски долгое в основном треде делать нельзя.

unDEFER ★★★★★
()

Как уже сказали, надо выносить длительные процессы в отдельный поток, и оттуда дёргать сигналы, по которым будет обновляться интерфейс. Но есть способ проще: вставить в свой длительный процесс вызовы QApplication::processEvents(). Это костыль, не рекомендуется, и всё такое. Но работает.

Beewek ★★★
()

Есть список серверов. В таблице.

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

  1. Сколько, хотя бы примерно, этих серверов? 5-10? 20-50? Под сотню?

  2. Ты в прошлой теме не ответил, ты этот QTableWidget используешь только для вывода или для ввода тоже?

Если ты ГУЁВЫЙ класс будешь использовать для обработки данных – тебе, да, никакая многопоточность не поможет.

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

Пробовал сигнал QProcess::readyReadStandardOutput() коннектить к слоту и в слоте читать выхлоп curl

Так vProcess->waitForFinished() нужно убрать, если пользуешься сигналом.

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

QTableWidget используется только для отображения списка серверов и их работоспособности. Список состоит из ~100 серверов. Данные вручную в таблицу не вносятся, никакой обработки данных в таблице не производится.

К таблице никаких претензий нет.

Мне нужно, чтобы программа не замерзала, пока в цикле работает процесс curl. Программа замерзает независимо от наличия таблицы. Кнопки не нажимаются, текстовые поля не выводят текст и т.д. Ни один из элементов не реагирует ни на мышь, ни на клавиатуру.

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

Данные вручную в таблицу не вносятся

Тогда тебе нужно хранить этот список во внешнем контейнере, например, объявил структуру ServInfo, и над ней QVector<ServInfo> или std::vector<ServInfo>, не принципиально. А табличный виджет один раз заполнил из этого контейнера и больше туда не лазь. Да, если тебе в этом виджете надо обновлять состояние – потом, как обошёл все сервера, так и обновишь (вот честно, мне кажется, что тут элегантнее было бы сделать класс ServerModel и связать с ней QTableView, но дело твоё).

никакой обработки данных в таблице не производится

Да? А я вот вижу, что у тебя указатель на QTableWidget дёргается в каждой итерации медленного цикла, того самого, в котором скрипт вызывается.

Если же ты хочешь, чтобы в самой таблице что-то правилось по мере опроса серверов – из опрашивающего потока посылай гуёвому потоку (а именно окну с таблицей) сигнал «сервер N раздуплился», окно будет перерисовываться.

А ты ГУИ и медленную логику запихнул не то, что в один поток, а вообще в один цикл. И фризам удивляешься.

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

vProcess->waitForFinished() это коннект к серверу, мне от этого процесса не нужно ничего, только сам факт что процесс отработал. Никакого выхлопа от него не нужно.

Выхлоп нужен от процесса curlProcess. Если выношу чтение stdout в слот

 connect(curlProcess, SIGNAL (readyReadStandardOutput()), this, SLOT(readOutput()));
и убираю curlProcess->waitForFinished() то окно программы по-прежнему не реагирует. Но вдобавок ещё и данные от курл не приходят.

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

Оба waitForFinished тормозят нещадно. От обоих нужно избавляться. Кстати, надеюсь, ты не думаешь, что curl будет выполняться на удалённой машине?

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

В том и проблема, что вынос цикла в отдельный поток не избавляет от фриза. Вот простейший пример:

class WorkerThread : public QThread {
    Q_OBJECT
protected:
    void run() override {
        for (int i=0; i<10; i++){
            qDebug() << "Работаем в потоке" << i;
            QThread::sleep(2); 
        }
    }
};

void MyClass::btnThreadClicked(){
    WorkerThread *thread = new WorkerThread();
    thread->start();
}

Нажал кнопку, поток запустился и всё замерзло, пока цикл не отсчитает эти 20 секунд.

Никаких обработок, никаких скриптов, никаких QTableWidget. Ничего. Просто пустой цикл полностью фризит программу

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

Я не могу избавиться от waitForFinished, потому что

а) мне нужно дождаться окончания процесса коннекта, прежде чем вызывать curl.

б) в выхлопе curl меня интересует только последние 15 байт, и мне нужно все равно ждать до самого конца. Если убираю curlProcess->waitForFinished(), то этих последних 15 бай (это сам ip) тупо отсутствуют. И при это фризы никуда не деваются. Ещё раз: фризы никуда не деваются даже если убрать curlProcess->waitForFinished()

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

самое простое - события, основа qt, даже waitforfineshed писать не надо )

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

мне нужно дождаться окончания процесса коннекта, прежде чем вызывать curl.

Тогда QFuture в помощь.

даже если убрать curlProcess->waitForFinished()

Окей, и на какой строчке теряется всё время в таком случае? readAllStandardOutput() должен быть в обработчике сигнала.

Если убираю curlProcess->waitForFinished(), то этих последних 15 бай (это сам ip) тупо отсутствуют

Используй сигнал QProcess::finished и только в нём читай вывод процесса.

А вообще, возьми какой-нибудь чатгпт, он тебе живо накидает код под твои хотелки.

unC0Rr ★★★★★
()
Последнее исправление: unC0Rr (всего исправлений: 1)
Ответ на: комментарий от Chord
  1. Я надеюсь, в реальном коде WorkerThread описан не рядом с MyClass, а в отдельном файле? Как минимум, это необходимо для нормальной работы moc.

  2. Вывод в qDebug() у меня тоже вызывает подозрения, это хоть и не G, но всё-таки UI. Впрочем, на этом сильно не настаиваю, вполне могу ошибаться. Но потокам всё-таки пристало общаться при помощи сигналов, а не вот это вот всё.

В остальном у меня аналогичные решения вполне работают. Без фризов.

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

Окей, и на какой строчке теряется всё время в таком случае?

Все время уходит на ожидание пока отработает curl ifconfig.me

Это порядка 4 секунд. Те миллисекунды, которые работает сама программа оптимизировать смысла нет. Цикл быстрее работать не станет. 99,9% времени это ожидание возврата curl

Без разницы, из программы я вызываю curl (через QProcess) или набираю вручную в консоли - 4 секунды ждешь ответа от ifconfig.me.

От того, что я откажусь от waitForFinished и перейду на QProcess::finished curl быстрее не отработает. Сайт ifconfig.me быстрее не ответит.

Я там выше привел пример - пустой цикл в отдельном потоке вешает всю программу до тех пор, пока цикл не закончится. Там никаких слотов, сигналов, процессов - ничего. Только пустой цикл. И он вешает программу. Почему?

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

Все время уходит на ожидание пока отработает curl ifconfig.me

Ну то есть на curlProcess->waitForFinished() ?

curl быстрее не отработает

Мы вроде не курл тут ускоряем, а боремся с замерзанием гуя?

пример - пустой цикл в отдельном потоке вешает всю программу до тех пор, пока цикл не закончится

Это с WorkerThread? Я скопировал этот код, у меня не вешает.

unC0Rr ★★★★★
()

Зачем вообще curl вызывать ради одного HTTP запроса?

HTTP-запрос можно же отправить средствами Qt.

Ja-Ja-Hey-Ho ★★★★★
()
Ответ на: комментарий от unC0Rr

Это с WorkerThread? Я скопировал этот код, у меня не вешает.

Да, именно этот пример. Пока счетчик до 20 не досчитает - окно ни на что не реагирует. Ни контролы в окне, ни само окно. Даже не закрывается по крестику. На всякий случай проверил ThreadID, а вдруг не создает новый поток, а крутит в старом - нет, у главного потока и у потока с циклом id разные. Дело явно не QProcess и его сигналах/слотах

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

QT += thread
Попробовал вставить - пишет, что нет такого модуля thread

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

Пытался накостылить через построчную запись в файл, чтобы оттуда потом считать через tail - и в файл тоже не пишет, пока цикл не закончится, а потом все строки разом записывает

Chord ★★★★★
() автор топика

Как вариант, можно не создавать потоки, в libcurl есть возможность привязать обработку запросов к Event Loop-у с помощью CURLM.

https://curl.se/libcurl/c/libcurl-multi.html

В случае Qt, тебе нужно будет завернуть CURL-овские файловые дескрипторы в QSocketNotifier. Вот, пример, как привязать к epoll https://curl.se/libcurl/c/ephiperfifo.html, для Qt просто делаешь по аналогии.

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

У меня работает, ты что-то не так делаешь.

unC0Rr ★★★★★
()

Можно и в один поток(сомневаюсь что там адские гигабайты обрабатываются) но тут походу жесткое непонимание чего то очень банального. Гуй не рисуется потому что он ждёт скорее всего. А код написан всрато. Ещё раз говорю: ТЗ, цена вопроса, дня два на код неделя на тесты.

ckotctvo
()

Есть два пути, это переместить объект moveToThread и тогда все его события будут в отдельном потоке и сигналы слоты будут работать причем потокобезопасно если connect сделать после moveToThread либо указать явно

Второй путь, если молотишь «как не правильно» в переопределенном run, тогда process messages после signal позволяет обрабатывать отправки сигналов из этого потока

Буквально месяца два назад это делал, надо было отдельный поток в программе Qt и всё получилось

I-Love-Microsoft ★★★★★
()
Ответ на: комментарий от Chord

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

I-Love-Microsoft ★★★★★
()
Ответ на: комментарий от ckotctvo

Не спасет никакой супер процессор, если у тебя на законных основаниях поток GUI просто спит пока идет прием скажем ста байт из UART, если программа написана неверно

I-Love-Microsoft ★★★★★
()

Короче используй QFuture. Там выполняй свой цикл сделай структуру results лучше возвращать QSharedPointer на нее. По-моему у нее есть сигнал что готов новый результат

ckotctvo
()

Гуй должен быть в своем ивент лупе.

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

Для опроса серверов должен иметься тредпул, который будет асинхронно (Qt::QueuedConnection) получать сигналы, пинаемые таймаутами таймеров, для старта тасков по опросу каждого сервера отдельно.

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

А не вот это вот всё...

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

А не вот это вот всё...

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

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

Посмотри в ветке develop http://github.com/Jurik-Phys/libvirt-snap-manager файлы snapManager.cpp vmDataCillector.cpp на тему qemu-img, там решается этот вопрос через QEventLoop, connect с лямбда функцией и выходом из loop'a по завершении работы лямбда функции

П.С. пишу с телефона, интернет не полноценный, примеры скопировать не могу

Jurik_Phys ★★★★★
()
Ответ на: комментарий от deep-purple

Гуй должен быть в своем ивент лупе.

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

Для опроса серверов должен иметься тредпул, который будет асинхронно (Qt::QueuedConnection) получать сигналы, пинаемые таймаутами таймеров, для старта тасков по опросу каждого сервера отдельно.

примерно да. Тред-пул опция, опрос или в отдельном треде или в green-thread или корутина, но он не должен сильно втормаживать GUI цикл приложения и должен пнуть модель, а она доведёт изменения до всех вьюх..

MKuznetsov ★★★★★
()
Последнее исправление: MKuznetsov (всего исправлений: 2)
Ответ на: комментарий от I-Love-Microsoft

Я бы попробовал сделать через QThreadPool и QRunnable.

u-235
()
Ответ на: комментарий от ckotctvo

Да, тоже хороший вариант. Я, если честно, не прочитал, что @Chord запускает curl в отдельном процессе, думал, что он использует libcurl в блокирующем режиме.

Я хотел сказать в целом, что эта задача вполне решается в рамках однопоточной прогаммы.

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

Да, это сработало! С moveToThread работает. Никаких фризов, значение в таблице пополняются на каждой итерации цикла. Спасибо!

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

Да, это сработало! С moveToThread работает. Никаких фризов,

Такой отвратительный дизайн… Вначале сделал алгоритм в виде цикла, потом вызвал целый процесс вместо запроса урла, а напоследок это всё запихнул в поток, чтобы не «тормозило»… 🤢 И это в реактивном кьюти, где всё прекрасно работает по сигналам и в одном потоке.

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

Да, я понял, прочитать все сообщения в этом треде - это не твое.

Вы ошиблись, я прочитал всё. Но особенно всё стало плохо, когда, вопреки всем советам, вы выбрали moveToThread().

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

Программа будет ждать заверешния appName без заморозки интерфейса при вызове doSometing()

void YourClass::doSomething(){
                                                    
    QProcess process;
    QEventLoop loop;

    QObject::connect(&process, &QProcess::finished, [&](){
                loop.quit();
            });

    process.start("appName", {"firstArg", "secondArg"});
    loop.exec();
}
Jurik_Phys ★★★★★
()
Последнее исправление: Jurik_Phys (всего исправлений: 2)
Ответ на: комментарий от raspopov

Плохо читал, либо читал и не понял. Иди ещё читай. А пока, чтоб ты меня не донимал - в игнор тебя.

Chord ★★★★★
() автор топика
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.