Russian Qt Forum

Qt => Многопоточное программирование, процессы => Тема начата: markie от Февраль 20, 2013, 20:59



Название: Высылка сигнала из потока
Отправлено: markie от Февраль 20, 2013, 20:59
Всем привет! Не могу понять, почему сигналы из потока (не основного) не высылаются на каждой итерации цикла? Например, есть следующий код:
Код:
while (1)
{
    ....
    emit progress();
    ....
}

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

P.S. Код выноситься в отдельный поток с помощью moveToThread, наследования от QThread нет.


Название: Re: Высылка сигнала из потока
Отправлено: ViTech от Февраль 20, 2013, 21:27
Threads and QObjects (http://qt-project.org/doc/qt-4.8/threads-qobject.html), в частности "Signals and Slots Across Threads". Чтобы основной поток обработал события, после emit progress() нужно вызвать QCoreApplication::processEvents. Только надо подумать, надо ли так часто основной поток дергать.


Название: Re: Высылка сигнала из потока
Отправлено: Igors от Февраль 21, 2013, 12:43
Threads and QObjects (http://qt-project.org/doc/qt-4.8/threads-qobject.html), в частности "Signals and Slots Across Threads". Чтобы основной поток обработал события, после emit progress() нужно вызвать QCoreApplication::processEvents. Только надо подумать, надо ли так часто основной поток дергать.
По-моему QCoreApplication::processEvents будет извлекать события из EventLoop текущего потока. А если главная нитка висит на своем EventLoop, то зачем еще processEvents?

Вот если по каким-то причинам while крутится в главной нитке - получится именно описанный эффект. Это легко проверить напечатав QThread::currentThreadId в главной нитке и while


Название: Re: Высылка сигнала из потока
Отправлено: ViTech от Февраль 21, 2013, 13:13
По-моему QCoreApplication::processEvents будет извлекать события из EventLoop текущего потока. А если главная нитка висит на своем EventLoop, то зачем еще processEvents?
Мы не знаем, что там в каком потоке происходит, и какими сигналами связаны. Вот пусть автор темы и разберется как работают потоки, как между ними сообщения проходят, и что делать, если "сигналы доходят до основного потока только после выхода из цикла".

Судя по
Цитировать
P.S. Код выноситься в отдельный поток с помощью moveToThread, наследования от QThread нет.
его еще много интересных открытий ожидает :).


Название: Re: Высылка сигнала из потока
Отправлено: twp от Февраль 21, 2013, 14:54
И сигналы progress, которые должны были высылаться на каждой итерации, доходят до основного потока только после выхода из цикла. Может быть кто-нибудь знает, с чем это может быть связано?

P.S. Код выноситься в отдельный поток с помощью moveToThread, наследования от QThread нет.

Цитата: Assistant
void QThread::run () [virtual protected]
The starting point for the thread. After calling start(), the newly created thread calls this function. The default implementation simply calls exec().
В exec() происходит отсылка сигналов. То получается что цикл крутится, а сигналы накапливаются в очереди. Как только цикл завершается, exec() отрабатывает и заваливает сигналами основной поток :)


Название: Re: Высылка сигнала из потока
Отправлено: markie от Февраль 21, 2013, 15:03
QCoreApplication::processEvents - это какой-то грязный хак в моей ситуации.

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

Может быть, кто-нибудь знает каким ещё способом можно организовать то, что требуется?
P.S. На форуме нашёл только это -  http://www.prog.org.ru/topic_23475_0.html (http://www.prog.org.ru/topic_23475_0.html). Там упомянули торрент-клиент из примеров Qt, но я так и не смог полностью понять, как а нём устроено обновление прогрессбара.


Название: Re: Высылка сигнала из потока
Отправлено: Странник от Февраль 21, 2013, 15:16
В exec() происходит отсылка сигналов. То получается что цикл крутится, а сигналы накапливаются в очереди. Как только цикл завершается, exec() отрабатывает и заваливает сигналами основной поток :)
в exec() происходит обработка входящих сигналов и вызов слотов. исходщие сигналы на него не завязаны, насколько я помню. кроме того, у автора exec() вызывается до запуска цикла в работу, если он верно использует moveToThread.

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


Название: Re: Высылка сигнала из потока
Отправлено: Igors от Февраль 21, 2013, 15:22
Что нужно сделать: из потока, который занимается какой-либо длительной операцией, нужно управлять прогрессбаром.
Соответственно, первое, что пришло в голову - высылка сигнала из потока.
Да, это и есть самое простое и правильное - если у Вас действительно while в другой (не главной) нитке  :)


Название: Re: Высылка сигнала из потока
Отправлено: markie от Февраль 21, 2013, 15:54
Попробую выложить минимальный пример.

Client - главный поток. Создание треда:
Код:
void Client::createThread(LongAction *action)
{
    QThread *newThread = new QThread(this);
    action->moveToThread(newThread);

    connect(action, SIGNAL(setRange(int, int)),         SLOT(setRangeForProgressBar(int, int)));
    connect(action, SIGNAL(progress(int)),              SLOT(setValueForProgressBar(int)));
    connect(action, SIGNAL(addToLog(QString, bool)),    SLOT(addLog(QString, bool)));
    connect(action, SIGNAL(finished(QString)),          SLOT(structureDownloadFinished(QString)));

    newThread->connect(action, SIGNAL( finished() ), SLOT( quit() ));
    newThread->start();
}

Вот ф-ция, в которой происходит скачка файла. Класс DownloadAction наследуется от LongAction (А LongAction от QObject).
Код:
void DownloadAction::readResponse()
{
    QTcpSocket *clientSocket = (QTcpSocket*)sender();
    QDataStream in(clientSocket);

    for (;;)
    {
        if (!_nextBlockSize)
        {
            if (clientSocket->bytesAvailable() < sizeof(quint32))
                break;

            in >> _nextBlockSize;
        }

        if (clientSocket->bytesAvailable() < _nextBlockSize)
            break;

        if (!_responseCode)
        {
            in >> _responseCode;

            QString info;
            QString fileName;
            int range = 0;

            if (_responseCode == 150)
            {
                in >> fileName >> _fileSize >> range;

                emit setRange(_rowProgressBar, range);

                if (!QFile::exists("download_files"))
                {
                    QDir dir(QDir::currentPath());
                    dir.mkdir("download_files");
                }

                QString filePath = QDir::currentPath() + "/download_files/" + fileName;
                _receiveFile->setFileName(filePath);

                if (!_receiveFile->open(QFile::WriteOnly))
                {
                    emit addToLog("Error open file " + _receiveFile->fileName() + " : " + _receiveFile->errorString(), true);
                    return;
                }

                _nextBlockSize = 0;
                continue;
            }
            else if (_responseCode == 450)
            {
                in >> info;
                continue;
            }

            QString response = "Response: " + QString::number(_responseCode);
            if (!info.isEmpty())
                response += ": " + info;

            emit addToLog(response);
        }

        char data[_nextBlockSize];
        int outData = in.readRawData(data, sizeof(char) * _nextBlockSize);
        _receiveFile->write(data, sizeof(char) * outData);
        _receivingBytes += outData;

        emit progress(_rowProgressBar);
        _nextBlockSize = 0;

        if (_receivingBytes == _fileSize)
        {
            emit addToLog("File " + _receiveFile->fileName() + " received");
            _receiveFile->close();
            emit finished();

            return;
        }
    }
}

readResponse() - как уже наверно все поняли слот, который подвешен на сигнал readyRead().
_rowProgressBar - номер строки в QTableWidget (в нём на каждой строчке располагаются прогрессбары).

P.S. сократил как мог, если что-то понадобится для наилучшего понимания, то пишите.


Название: Re: Высылка сигнала из потока
Отправлено: ViTech от Февраль 21, 2013, 16:29
Client::createThread и DownloadAction::readResponse где вызываются? Вы уверены, что код фактически выполняется в тех потоках, в которых Вы задумывали? Выше уже предлагали проверить с помощью QThread::currentThreadId.


Название: Re: Высылка сигнала из потока
Отправлено: markie от Февраль 21, 2013, 18:53
Client - главный поток. currentThreadId() в слоте readResponse и в объекте Client разный.


Название: Re: Высылка сигнала из потока
Отправлено: ViTech от Февраль 21, 2013, 19:28
По коду много еще всяких вопросов возникает, но, наверное, полезно будет по граблям походить. Так частенько, в конечном итоге, толка больше получается, чем если сразу все правильно делать :).

Цитировать
_rowProgressBar - номер строки в QTableWidget (в нём на каждой строчке располагаются прогрессбары).
А этот прогрессбар откуда данные для текущей позиции берет? Можно ли сразу в emit progress() передать текущую позицию? Тогда и action'у не надо знать, какой там прогрессбар к нему будет подключаться, и прогрессбару без разницы, кто ему текущую позицию сообщать будет. Можно еще выяснить, как часто будет вызываться emit progress(), и надо ли с такой частотой прогрессбар обновлять.

И, если можно, такой оффтопный интимный вопрос: до знакомства с Qt, на каких языках писали, с какими фреймворками работали? :)


Название: Re: Высылка сигнала из потока
Отправлено: markie от Февраль 21, 2013, 21:44
>А этот прогрессбар откуда данные для текущей позиции берет?
Вызывается метод value и инкрементируется текущее значение.

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

>на каких языках писали, с какими фреймворками работали?
C, C++ . Совсем чуть-чуть работал с mfc, вот сейчас кьют.


Название: Re: Высылка сигнала из потока
Отправлено: Old от Февраль 21, 2013, 22:07
2 markie: Скажите, а что пишется в консоль при выполнении вашей программы?

Смущает меня эта строка:
Код
C++ (Qt)
newThread->connect(action, SIGNAL( finished() ), SLOT( quit() ));

Если вы хотите, что бы нить завершалась, при завершении загрузки, то нужно так:
Код
C++ (Qt)
connect(action, SIGNAL( finished() ), newThread, SLOT( quit() ));


Название: Re: Высылка сигнала из потока
Отправлено: markie от Февраль 22, 2013, 01:46
2Old: эти 2 записи эквивалентны, соответственно в консоль ошибок не выдаётся.


Название: Re: Высылка сигнала из потока
Отправлено: carrygun от Февраль 22, 2013, 07:16
А где код чтото в духе:
Код
C++ (Qt)
connect(newThread, SIGNAL( started() ), action, SLOT( doSomeHugeWork() ));
То есть тред будет весеть в экзеке и все. Работа action'а быдет выполнена в том треде, где она будет вызвана, поэтому ее надо вызывать созданным тредом. По представленному коду не видно где вызвается это самая "работа", смею предположить что тред болтается сам по себе, а работа вызвается в главном потоке. Отсюда и такой эффект


Название: Re: Высылка сигнала из потока
Отправлено: Igors от Февраль 22, 2013, 12:21
Попробую выложить минимальный пример.
...
P.S. сократил как мог, если что-то понадобится для наилучшего понимания, то пишите.
Ну вот допустим кто-то хочет помочь, что он должен делать с таким "минимальным примером"? Создать тестовое приложение, хедеры для Ваших классов, откуда-то догадаться об их ф-ционале и многое др. Вы бы стали этим заниматься? Ну так почему думаете кто-то другой станет? Вот если бы исходники + pro файл, тогда желающие нашлись бы. Понятно что Вам проще вырезать что-то из исходников чем возиться с каким-то тестовым проектом - ну так вырезка-то ничего не дает.

В большинстве случаев автор находит ошибку сам в процессе подготовки тестового проекта