Russian Qt Forum

Qt => Общие вопросы => Тема начата: zodiac от Ноябрь 07, 2008, 16:05



Название: Завершнение QThread
Отправлено: zodiac от Ноябрь 07, 2008, 16:05
Привествую всех. Возникла большая проблемка с завершнением qthread, а затем с его повторным запуском. Имеется клиент джаббера и при смене статуса на оффлайн (точнее, когда пришло сообщение от либы о том, что нас отключили от сервера) поток должен завершаться (делаю это так: this->exit();), но затем, если опять пробовать подключиться (сразу), то "this->isRunning()" выдает true. Как сделать так, чтобы как-то ждать. пока поток завершиться? Пробовал "connect(this, SIGNAL(finished()), this, SLOT(threadFinished()));", но оно как-то у меня не всегда рабатывает =|


Название: Re: Завершнение QThread
Отправлено: vaprele07 от Ноябрь 07, 2008, 16:12
while (this->isRunning()) ; правда опасно  :o


Название: Re: Завершнение QThread
Отправлено: zodiac от Ноябрь 07, 2008, 16:17
this->exit();
while (this->isRunning());
Вешает программу:)


Название: Re: Завершнение QThread
Отправлено: BRE от Ноябрь 07, 2008, 16:37
this->exit();
while (this->isRunning());
Вешает программу:)

Попробуй место: while (this->isRunning());
QThread::wait(...) использовать.


Название: Re: Завершнение QThread
Отправлено: zodiac от Ноябрь 07, 2008, 16:48
Что-то я не пойму как его использовать...


Название: Re: Завершнение QThread
Отправлено: BRE от Ноябрь 07, 2008, 17:04
Что-то я не пойму как его использовать...
Использовать так:
Код:
	exit();
wait();

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

P.S. Да и желательно проверять код возврата, если false - то за указанное время нить так и не умерла.


Название: Re: Завершнение QThread
Отправлено: zodiac от Ноябрь 07, 2008, 17:16
В первый раз это срабатывает нормально (после первого отключения), но во второй раз (после второго) уже идет ступор у него%)


Название: Re: Завершнение QThread
Отправлено: BRE от Ноябрь 07, 2008, 17:24
В первый раз это срабатывает нормально (после первого отключения), но во второй раз (после второго) уже идет ступор у него%)
А после рестарта нить нормально работает?
Получается, что нить второй раз не финиширует? Я правильно понимаю.


Название: Re: Завершнение QThread
Отправлено: SASA от Ноябрь 07, 2008, 17:41
Код:
while (thread->isRunning())
{
QCoreApplication::processEvents(QEventLoop::AllEvents);
}
А где ты делаешь exit(); wait(); и в каком потоке?


Название: Re: Завершнение QThread
Отправлено: zodiac от Ноябрь 07, 2008, 17:42
В первый раз это срабатывает нормально (после первого отключения), но во второй раз (после второго) уже идет ступор у него%)
А после рестарта нить нормально работает?
Получается, что нить второй раз не финиширует? Я правильно понимаю.
Да, правильно. Работает и потом не финиширует.


Название: Re: Завершнение QThread
Отправлено: zodiac от Ноябрь 07, 2008, 17:58
Код:
while (thread->isRunning())
{
QCoreApplication::processEvents(QEventLoop::AllEvents);
}
А где ты делаешь exit(); wait(); и в каком потоке?
Эмс... а откуда цикл вызывать?
Делаю при вызове "void jProtocol::onDisconnect(ConnectionError e)". В этом потоке, который нужно завершить.


Название: Re: Завершнение QThread
Отправлено: zodiac от Ноябрь 07, 2008, 18:04
Так. с обычным отключением сейчас все в порядке (вроде). Осталась проблема в либе..


Название: Re: Завершнение QThread
Отправлено: BRE от Ноябрь 07, 2008, 18:20
Так. с обычным отключением сейчас все в порядке (вроде). Осталась проблема в либе..
Набросал тестовый класс, вроде все ок.
Все приводить не буду, только класс Thread
thread.h
Код:
#ifndef THREAD_H
#define THREAD_H

#include <QThread>

class Thread : public QThread
{
    Q_OBJECT
public:
    Thread( QObject *parent );

    void    run();

public slots:
    void    connectReady();
    void    connectLost();

signals:
    void    alive();
};

#endif // THREAD_H

thread.cpp
Код:
#include "thread.h"
#include <QTimer>

Thread::Thread( QObject *parent ) :
    QThread( parent )
{
}

void Thread::run()
{
    QTimer tm;
    tm.setSingleShot( false );
    connect( &tm, SIGNAL( timeout() ), this, SIGNAL( alive() ) );

    tm.start( 200 );

    exec();
}

void Thread::connectReady()
{
    start();
}

void Thread::connectLost()
{
    exit();
    wait();
}

Что за проблемы в либе?


Название: Re: Завершнение QThread
Отправлено: zodiac от Ноябрь 07, 2008, 18:48
Вот мое:
Код:
void jProtocol::onDisconnect(ConnectionError e)
{
qDebug() << "exit";
TreeModelItem item;
item.m_protocol_name = "Jabber";
item.m_account_name = m_account_name;
item.m_item_type = 2;
if(e != ConnUserDisconnected && e != ConnNotConnected)
{
QString error_tr;
switch(e)
{
---тут ошибки и установка error_tr---
}
emit systemNotification(m_account_name,error_tr);
}
m_jabber_account->getPluginSystem().setAccountIsOnline(item, false);
m_jabber_roster->setOffline();
emit setRealStatus(Presence::Unavailable);
---Тут поток должен завершаться---
}
когда e == ConnUserDisconnected, то все нормально, если нет, то ненормально. Хотя "exit" выводит во всех случаях. Вот я и думаю мож чего в либе..


Название: Re: Завершнение QThread
Отправлено: BRE от Ноябрь 07, 2008, 19:36
когда e == ConnUserDisconnected, то все нормально, если нет, то ненормально. Хотя "exit" выводит во всех случаях. Вот я и думаю мож чего в либе..
Без рабочего кода трудно что-то проверить, я бы на твоем месте начал комментировать по частям и смотреть из-за чего конкретно это происходит.


Название: Re: Завершнение QThread
Отправлено: zodiac от Ноябрь 07, 2008, 20:29
Ну я комментил весь if, результат тот же:) Ладно, может попозже посмотрю или завтра. А если я код дам, то не лень будет посмотреть?


Название: Re: Завершнение QThread
Отправлено: BRE от Ноябрь 07, 2008, 20:42
Ну я комментил весь if, результат тот же:) Ладно, может попозже посмотрю или завтра. А если я код дам, то не лень будет посмотреть?
Если сам не разберешься, выкладывай. Чем смогу - помогу.


Название: Re: Завершнение QThread
Отправлено: zodiac от Ноябрь 08, 2008, 13:32
Не разобрался :(
Пишет:
Код:
QThread::wait: Thread tried to wait on itself

Вот код: http://92.63.106.179/svn/jabber/src/jProtocol.cpp
Функции:
Код:
void jProtocol::setStatus(Presence::PresenceType presence, QString message)
void jProtocol::run()
void jProtocol::onDisconnect(ConnectionError e)


Название: Re: Завершнение QThread
Отправлено: BRE от Ноябрь 08, 2008, 14:04
Не разобрался :(
Пишет:
Код:
QThread::wait: Thread tried to wait on itself

Вот код: http://92.63.106.179/svn/jabber/src/jProtocol.cpp
Функции:
Код:
void jProtocol::setStatus(Presence::PresenceType presence, QString message)
void jProtocol::run()
void jProtocol::onDisconnect(ConnectionError e)
А как вызывается onDisconnect?
P.S. Посмотри код QThread::wait(), думаю вопрос отпадет.


Название: Re: Завершнение QThread
Отправлено: ритт от Ноябрь 08, 2008, 14:08
по идее, всё правильно...
попробуй закомментировать всё тело onDisconnect, кроме выхода; и всё тело setStatus, кроме запуска.

как я понял, jClient->disconnect() зовёт jProtocol::onDisconnect(ConnectionError e) ?


Название: Re: Завершнение QThread
Отправлено: BRE от Ноябрь 08, 2008, 14:22
Нужно уезжать, поэтому кратко.
Ты вызываешь wait в контексте останавливаемой нити, а нужно из контекста другой (например главной) нити.


Название: Re: Завершнение QThread
Отправлено: zodiac от Ноябрь 08, 2008, 15:09
А как вызывается onDisconnect?
P.S. Посмотри код QThread::wait(), думаю вопрос отпадет.
Вызывается глуксом.
В смысле посмотреть?

по идее, всё правильно...
попробуй закомментировать всё тело onDisconnect, кроме выхода; и всё тело setStatus, кроме запуска.

как я понял, jClient->disconnect() зовёт jProtocol::onDisconnect(ConnectionError e) ?
Должен звать, да. так как "exit" выводится во всех случаях.

Нужно уезжать, поэтому кратко.
Ты вызываешь wait в контексте останавливаемой нити, а нужно из контекста другой (например главной) нити.
Ок, попробую через слот/сигнал сделать.
Т.е. из главной нити звать wait(), а exit() можно из самой?


Название: Re: Завершнение QThread
Отправлено: zodiac от Ноябрь 08, 2008, 18:22
Вынес. Только щас когда идет подключение, то поток не завершается. Зато в остальных случаях завершается.

Хотел сделать так:
Код:
	if (!m_jabber_protocol->wait(1000))
m_jabber_protocol->terminate();
Но ругается:
Цитировать
Qt has caught an exception thrown from an event handler. Throwing
exceptions from an event handler is not supported in Qt. You must
reimplement QApplication::notify() and catch all exceptions there.


Название: Re: Завершнение QThread
Отправлено: BRE от Ноябрь 08, 2008, 19:00
Можно попробовать сделать отдельную нить, которая будет следить за состоянием всех соединений. Если происходит подключение, то запускает требуемую нить, если отключение - останавливает.

Только щас когда идет подключение, то поток не завершается.
Не совсем понял?


Название: Re: Завершнение QThread
Отправлено: zodiac от Ноябрь 08, 2008, 19:31
Можно попробовать сделать отдельную нить, которая будет следить за состоянием всех соединений. Если происходит подключение, то запускает требуемую нить, если отключение - останавливает.

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


Название: Re: Завершнение QThread
Отправлено: BRE от Ноябрь 08, 2008, 19:48
Ну когда идет подключение к серверу джаббера:) Что-то там поток не всегда хорошо останавливается, видать передача данных так долго идет. Но только не понятно, чего же оно гуй-то вешает.
Я бы на твоем месте повтыкал побольше qDebug() << "....", буквально после каждого логического действия.
Типа:
Код:
void jProtocol::onDisconnect(ConnectionError e)
{
qDebug() << "exit";
TreeModelItem item;
item.m_protocol_name = "Jabber";
item.m_account_name = m_account_name;
item.m_item_type = 2;

qDebug() << "check error"; // <<<<<<<<<<<<<<<<<<<<<<
if(e != ConnUserDisconnected && e != ConnNotConnected)
{
QString error_tr;
switch(e)
{
...
default:
error_tr = tr("Unknown error. It is amazing that you see it... O_o");
break;
}
qDebug() << "emit systemNotification"; // <<<<<<<<<<<<<<<<<<<<<<<<<
emit systemNotification(m_account_name,error_tr);
}//zodiac.test@jabber.egghost.ru

qDebug() << "setAcountInOnlene()"; // <<<<<<<<<<<<<<<<<<<<<<<
m_jabber_account->getPluginSystem().setAccountIsOnline(item, false);

qDebug() << "setOffline()"; // <<<<<<<<<<<<<<<<<<<<<<<
m_jabber_roster->setOffline();

// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< и т.д.
emit setRealStatus(Presence::Unavailable);
exit();
wait();
}
И тогда уже бы смотрел, что там происходит.
Задержки могут быть и в обработчиках сигналов...


Название: Re: Завершнение QThread
Отправлено: zodiac от Ноябрь 08, 2008, 20:00
Я щас сделал так:
Код:
	emit setRealStatus(Presence::Unavailable);
qDebug() << "exit2";
emit threadWait();
И в jAccount.cpp:
Код:
void jAccount::threadWait()
{
qDebug() << "threadWait()";
m_jabber_protocol->exit();
if (!m_jabber_protocol->wait(1000))
m_jabber_protocol->terminate();
}
Вся фигня с wait(). Может как-нибудь с тобой можно по IM связаться? А то так на форуме долго:-)


Название: Re: Завершнение QThread
Отправлено: ритт от Ноябрь 08, 2008, 20:16
> Задержки могут быть и в обработчиках сигналов...
между потоками? ну-ну :)

zodiac, мне вообще не понятно почему выбрана именно такая архитектура...зачем нужен этот полудохлый поток, когда юзверь отключился от сервера (или ещё не подключился)? а если я-пользователь захочу 2 плагина жаббера (чтобы пользоваться двумя акками одновременно), у меня будет 2 абсолютно одинаковых потока, отличающихся только аккаунтами?
м.б. резоннее было бы разделить класс на два: один для работы с клиентской стороной и управления потоками, другой - непосредственно поток, общающийся с сервером. m_account_name можно оставить в потоке, раз уж    TreeModelItem везде создаётся в стёке и отличается только именем акка. юзверь отключился от сервера - поток можно смело гасить, т.к. нет смысла ждать событий от сервера...и не надо даже ждать смерти потока, т.к. новое подключение создаст новый поток - в случае внезапного обрыва не придётся ждать таймаута чтобы переподключиться (что меня раздражает в миранде). и гуй при таком раскладе не будет вешаться, даже если потоки по каким-то загадочным причинам вовсе не хотят подыхать...а в худшем случае их можно киллить терминатом по таймеру :)


Название: Re: Завершнение QThread
Отправлено: zodiac от Ноябрь 08, 2008, 20:40
zodiac, мне вообще не понятно почему выбрана именно такая архитектура...зачем нужен этот полудохлый поток, когда юзверь отключился от сервера (или ещё не подключился)? а если я-пользователь захочу 2 плагина жаббера (чтобы пользоваться двумя акками одновременно), у меня будет 2 абсолютно одинаковых потока, отличающихся только аккаунтами?
м.б. резоннее было бы разделить класс на два: один для работы с клиентской стороной и управления потоками, другой - непосредственно поток, общающийся с сервером. m_account_name можно оставить в потоке, раз уж    TreeModelItem везде создаётся в стёке и отличается только именем акка. юзверь отключился от сервера - поток можно смело гасить, т.к. нет смысла ждать событий от сервера...и не надо даже ждать смерти потока, т.к. новое подключение создаст новый поток - в случае внезапного обрыва не придётся ждать таймаута чтобы переподключиться (что меня раздражает в миранде). и гуй при таком раскладе не будет вешаться, даже если потоки по каким-то загадочным причинам вовсе не хотят подыхать...а в худшем случае их можно киллить терминатом по таймеру :)
Ну так выбрано потому, что каждое подключение делает while (пока_не_ошибка_от_сервера) и в этом цикле все принимается. Так что не получится 1 поток для общения с сервером или предлагаешь делать поток и уже из него запускать все остальные потоки? %) Хм...


Название: Re: Завершнение QThread
Отправлено: ритт от Ноябрь 08, 2008, 20:49
> while (пока_не_ошибка_от_сервера)
это типа эмуляции петли событий? ведь таким образом ты ограничиваешь всю архитектуру на использование производных потоков, что не очень-то гибко. а почему так, а не через полноценный QEventLoop?

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


Название: Re: Завершнение QThread
Отправлено: zodiac от Ноябрь 08, 2008, 20:55
> while (пока_не_ошибка_от_сервера)
это типа эмуляции петли событий? ведь таким образом ты ограничиваешь всю архитектуру на использование производных потоков, что не очень-то гибко. а почему так, а не через полноценный QEventLoop?
Так как библиотека написана на C++, без QT.

упд:
> пока_не_ошибка_от_сервера
означает ли это, что такой цикл имеется в каждом потоке соединения с сервером? т.е. в аська-протоколе свой цикл, в жаббер-протоколе - свой и т.д.?
Я джаббере -- в каждом потоке такой цикл, а аське же нет. Аська писалась на C++/QT без всяких сторонних библиотек.


Название: Re: Завершнение QThread
Отправлено: ритт от Ноябрь 08, 2008, 21:45
> while (пока_не_ошибка_от_сервера)
это типа эмуляции петли событий? ведь таким образом ты ограничиваешь всю архитектуру на использование производных потоков, что не очень-то гибко. а почему так, а не через полноценный QEventLoop?
Так как библиотека написана на C++, без QT.

упд:
> пока_не_ошибка_от_сервера
означает ли это, что такой цикл имеется в каждом потоке соединения с сервером? т.е. в аська-протоколе свой цикл, в жаббер-протоколе - свой и т.д.?
Я джаббере -- в каждом потоке такой цикл, а аське же нет. Аська писалась на C++/QT без всяких сторонних библиотек.

а, так это в библиотеке...
сервер-то всё-равно не может инициировать события, пока нет коннекта - зачем же тогда держать дохлый поток?
вот я и говорю: если функции в библиотеке потокобезопасные (thread-safe), то всю работу с протоколом можно спихнуть на поток, который будет жить вплоть до разрыва соединения (если не thread-safe, то можно написать потокобезопасный wrapper), всю работу по взаимодействию клиентской части с сервером - на менеджер таких потоков, т.е. как бы минуя вопросы протокола...

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

                              /--- акк_icq_1
   г--- плагин icq --- +--- акк_icq_2
  |                     /|\  \--- акк_icq_3
  |                      |
  |                     \|/
гуй --- менеджер соединений
  |                         /|\
  |                          |
  |                         \|/   /--- акк_jabber_1
   L--- плагин jabber --- +--- акк_jabber_2
                                   \--- акк_jabber_3

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

снова сравню с мирандой - там используется упрощённая модель: гуй <-> менеджер соединений <-> кучка потоков; но главное отличие, что гуй зависим от потоков - из-за этого часто случаются подвисания (например, при разрыве сединения в некоторых протоколах).
если все "внешние" операции производить в отдельном потоке, а связывать события с гуём только через очередь, гуй(основной поток) вообще будет заниматься только рисованием и взаимодействием с пользователем, не беспокоясь о проблемах сети и т.п.


Название: Re: Завершнение QThread
Отправлено: BRE от Ноябрь 08, 2008, 21:49
Можно попробовать сделать отдельную нить, которая будет следить за состоянием всех соединений. Если происходит подключение, то запускает требуемую нить, если отключение - останавливает.
Вроде это и предлагал, чуть выше.
+ Это позволит разрулить ситуацию с контекстами нитей. Запускаться и убиваться нити будут из одного контекста.


Название: Re: Завершнение QThread
Отправлено: zodiac от Ноябрь 08, 2008, 22:05
Аську вообще сюда не надо приплетать. Там никто ничего переписывать не будет уже :-)
В принципе щас можно разделить яббер на еще 1 поток и остальные потоки, которые будут запускаться из того (я правильно понял?), но это уже не сегодня.
Я не совсем понял как лучше сделать. Когда запускать этот поток? И как из него уже управлять другими.


Название: Re: Завершнение QThread
Отправлено: ритт от Ноябрь 08, 2008, 22:22
запускать такой поток (менеджер соединений) можно сразу при инициализации плагина, прибивать перед выгрузкой плагина. на запрос менеджеру о соединении с сервером создаётся и запускается ведомый поток, который прибивается при возникновении ошибки или нормальном дисконнекте. останавливать его и затем снова запускать практически не имеет смысла, но это уже не суть важно.
если предполагается, что такой менеджер занимается только жаббер-протоколом, то прямо в нём можно реализовать какие-то общие безопасные для дочерних потоков слоты.
как побочный эффект такого разделения появится возможность в одном плагине создавать и контролировать более одного соединения с сервером (серверами) - если архитектура qutim позволяет, в настройках плагина можно дать пользователю возможность указать несколько аккаунтов или добавить/удалить. ))


Название: Re: Завершнение QThread
Отправлено: BRE от Ноябрь 08, 2008, 22:49
> Задержки могут быть и в обработчиках сигналов...
между потоками? ну-ну :)
Моя запарка.
В моем тестовом примере, слоты выполнялись в контексте главного треда.  :)


Название: Re: Завершнение QThread
Отправлено: zodiac от Ноябрь 08, 2008, 23:42
если архитектура qutim позволяет, в настройках плагина можно дать пользователю возможность указать несколько аккаунтов или добавить/удалить. ))
Архитектура отдает все проблемы плагина -- плагину. Завтра днем посмотрю эту идею, если время хватит.


Название: Re: Завершнение QThread
Отправлено: naico от Ноябрь 10, 2008, 13:45
А подскажите средство в духе kill, чтобы действовало решительно и беспощадно:)


Название: Re: Завершнение QThread
Отправлено: pastor от Ноябрь 10, 2008, 13:57
А подскажите средство в духе kill, чтобы действовало решительно и беспощадно:)

Цитировать
void QThread::terminate ()   [slot]
Terminates the execution of the thread. The thread may or may not be terminated immediately, depending on the operating systems scheduling policies. Use QThread::wait() after terminate() for synchronous termination.
When the thread is terminated, all threads waiting for the thread to finish will be woken up.
Warning: This function is dangerous and its use is discouraged. The thread can be terminate at any point in its code path. Threads can be terminated while modifying data. There is no chance for the thread to cleanup after itself, unlock any held mutexes, etc. In short, use this function only if absolutely necessary.
Termination can be explicitly enabled or disabled by calling QThread::setTerminationEnabled(). Calling this function while termination is disabled results in the termination being deferred, until termination is re-enabled. See the documentation of QThread::setTerminationEnabled() for more information.


Название: Re: Завершнение QThread
Отправлено: naico от Ноябрь 10, 2008, 14:20
Спасибо, но мне это не помогает в случае, если в треде идет обращение к файловой системе удаленной машины, допустим, к несуществующей папке. Тогда тред ожидает (видимо по таймеру) ответа, и на terminate не реагирует.
Ситуация, конечно, нестандартная для работы программы, но все ж хочется ее обрабатывать, по крайней мере убивать процесс.


Название: Re: Завершнение QThread
Отправлено: pastor от Ноябрь 10, 2008, 14:34
В таком случае организовывайте собственные таймауты, и собственно "таймаут" и будет условием выхода из потока


Название: Re: Завершнение QThread
Отправлено: zodiac от Ноябрь 11, 2008, 21:41
Разобрался с потоками. Большое спасибо BRE :)