Russian Qt Forum

Qt => Общие вопросы => Тема начата: spectre71 от Июль 15, 2009, 13:24



Название: QThread - синхронизация с главным потоком.
Отправлено: spectre71 от Июль 15, 2009, 13:24
Задача.
1) Имеем объект некоторого класса, имеем метод который может быть вызван в любом потоке(в зависимости от ситуации), как в главном, так и в вспомогательном.
2) Выполнение данного метода может быть очень длительным и как правило для его выполненния создается вспомогательный поток
3) В данном методе есть вызов другого метода в котором происходит создание некоторого QWidget
4) Проблема в том что QWidget могут создаваться только в главном потоке.

Как сделать внутри второстепенного потока вызов метода в контексте главного потока?
В билдере у класса TThread для этих целей был "syncronize(method)", есть ли что-либо подобное в QT, а если нет, то как решить проблему?


Название: Re: QThread - синхронизация с главным потоком.
Отправлено: ритт от Июль 15, 2009, 17:15
в контексте QApplication или TL Widget приписать слот, создающий необходимый виджет. из п.3 асинхронно вызывать данный слот.

насколько я помню, багландовский syncronize замораживает задействованные потоки...


Название: Re: QThread - синхронизация с главным потоком.
Отправлено: alexman от Июль 15, 2009, 22:13
Например, можно послать сигнал из вспомогательного потока главному на создание QWidget и уснуть на QWaitCondition (метод bool wait ( QMutex * mutex, unsigned long time = ULONG_MAX )). Далее в главном окне что-то делаем с QWidget, а по завершению вызываем метод второстепенного потока из главного, в котором делаем wakeOne.


Название: Re: QThread - синхронизация с главным потоком.
Отправлено: spectre71 от Июль 16, 2009, 11:48
Спасибо Константин и alexman.
Вроде работает. Если есть желание, посмотрите что получилось, может что еще поскажете или найдете ошибки.
синхронизация делается через наследника SbThreadSync, вызовом статического метода SbThread::synchronize(SbThreadSync* s);
Еще сделан #define sb_synchronize_method. Можно делать синхронизацию так:

Код
C++ (Qt)
void MyClass::methodForSync(void) {
 ...
}
 
void MyClass::doAny(void) {
 ...
 ...
 sb_synchronize_method(MyClass, methodForSync);
 ...
 ...
}

Далее исходники
sbw_kernel.h
Код
C++ (Qt)
//---------------------------------------------------------------------------
 
#ifndef sbw_kernelH
#define sbw_kernelH
 
//---------------------------------------------------------------------------
 
#include <QObject>
#include <QApplication>
#include <QThread>
#include <QWaitCondition>
#include <QMutex>
 
/*-----------------------------------------------------------------------------*/
 
class SbThread;
class SbThreadSync;
class SbApplication;
 
/*-----------------------------------------------------------------------------*/
 
class SbThread : public QThread {
   Q_OBJECT
 protected:
   QAtomicInt Stopped;
   QMutex Mutex;
   QWaitCondition Wait;
   SbApplication* Application;
 public:
   SbThread(bool FreeOnComplete = false, QObject * parent = 0);
   virtual ~SbThread();
 
   inline bool stopped (void) const {return Stopped;}
   inline void stop    (void) {Stopped.ref();}
   void wake (void);
 
 public:
   static void synchronize(SbThreadSync* s);
 
 protected:
   void doSynchronize(SbThreadSync* s);
 
 signals:
   void signalSynchronize(SbThreadSync* s);
 
};
 
/*-----------------------------------------------------------------------------*/
 
class SbThreadSync {
   friend class SbThread;
   friend class SbApplication;
 private:
   SbThread* Thread;
 protected:
   void syncCall(void);
 public:
   SbThreadSync() {Thread = NULL;}
   virtual ~SbThreadSync() {}
 
   virtual void call(void) = 0;
};
 
#define sb_synchronize_method(__type, __method) \
 { \
   class TrSync : public SbThreadSync { \
     private : \
       __type* obj; \
       void (__type::*method)(void); \
     public : \
       TrSync(__type* obj, void (__type::*method)(void)): SbThreadSync() {this->obj = obj; this->method = method;} \
       void call (void) {(obj->*method)();} \
   } trs(this, &__type::__method); \
   SbThread::synchronize(&trs); \
 }
 
/*-----------------------------------------------------------------------------*/
 
class SbApplication : public QApplication {
   Q_OBJECT
 public:
   static QThread* thread(void) {return QApplication::instance()->thread();}
 public:
   SbApplication(int& argc, char** argv) : QApplication(argc, argv) {}
 
 private slots:
   void synchronize(SbThreadSync* s) {s->syncCall();}
};
 
#endif
 

sbw_kernel.cpp
Код
C++ (Qt)
/*-----------------------------------------------------------------------------*/
 
#include "sbw_kernel.h"
 
/*-----------------------------------------------------------------------------*/
 
/***************************
*     class SbThread
***************************/

 
SbThread::SbThread(bool FreeOnComplete, QObject* parent) : QThread(parent) {
 Stopped = 0;
 if(FreeOnComplete) {connect(this , SIGNAL(finished()), this , SLOT(deleteLater()));}
 SbApplication* Application = qobject_cast<SbApplication*>(QApplication::instance());
 if(Application)    {connect(this , SIGNAL(signalSynchronize(SbThreadSync*)), Application, SLOT(synchronize(SbThreadSync*)), Qt::QueuedConnection);}
}
 
SbThread::~SbThread() {
}
 
void SbThread::wake (void) {
 Wait.wakeOne();
}
 
void SbThread::doSynchronize(SbThreadSync* s) {
 Mutex.lock();
 emit signalSynchronize(s);
 Wait.wait(&Mutex);
 Mutex.unlock();
}
 
void SbThread::synchronize(SbThreadSync* s) {
 if(SbApplication::thread() == QThread::currentThread()) {
   s->call();
   return;
 }
 SbThread* Thread = qobject_cast<SbThread*>(QThread::currentThread());
 if(!Thread || !Thread->Application) {
   s->call(); // OR throw
   return;
 }
 s->Thread = Thread;
 Thread->doSynchronize(s);
}
 
/*-----------------------------------------------------------------------------*/
 
/***************************
*     class SbThreadSync
***************************/

 
void SbThreadSync::syncCall(void) {
 call();
 Thread->wake();
}
 
/*-----------------------------------------------------------------------------*/
 


Название: Re: QThread - синхронизация с главным потоком.
Отправлено: Winstrol от Июль 16, 2009, 16:35
  • Например, можно послать сигнал из вспомогательного потока главному на создание QWidget и уснуть на QWaitCondition (метод bool wait ( QMutex * mutex, unsigned long time = ULONG_MAX )). Далее в главном окне что-то делаем с QWidget, а по завершению вызываем метод второстепенного потока из главного, в котором делаем wakeOne.
    • Во-первых, help по Qt::ConnectionType.
    • Во-вторых, низкоуровневые примитивы вроде QMutex,QAtomicInt и.т.п. в прикладном коде следует всячески избегать, т.к. при наличии QtConcurrent они тупо практически никогда не нужны, а если и нужны, то часто есть более лучшие решения без них.
    • В-третьих, что будет
      Поток A шлет сигнал главному потоку сигнал  на создание QWidget и засыпает.
      Поток B шлет сигнал главному потоку сигнал  на создание QWidget и засыпает.
      Главный поток создает QWidget по запросу потока А и делает wakeOne.
      Вопрос? Какой поток проснется A или B.
    Цитировать
    void QWaitCondition::wakeOne ()
    Wakes one thread waiting on the wait condition. The thread that is woken up depends on the operating system's scheduling policies, and cannot be controlled or predicted.
    If you want to wake up a specific thread, the solution is typically to use different wait conditions and have different threads wait on different conditions.
    See also wakeAll().



Название: Re: QThread - синхронизация с главным потоком.
Отправлено: spectre71 от Июль 16, 2009, 19:07
Во-первых, help по Qt::ConnectionType.
Ну и что? Что плохого в том что я явно указал Qt::QueuedConnection

Во-вторых, низкоуровневые примитивы вроде QMutex,QAtomicInt и.т.п. в прикладном коде следует всячески избегать, т.к. при наличии QtConcurrent они тупо практически никогда не нужны, а если и нужны, то часто есть более лучшие решения без них.
Покажи решение лучше!

В-третьих, что будет
Поток A шлет сигнал главному потоку сигнал  на создание QWidget и засыпает.
Поток B шлет сигнал главному потоку сигнал  на создание QWidget и засыпает.
Главный поток создает QWidget по запросу потока А и делает wakeOne.
Вопрос? Какой поток проснется A или B.
проснется A!
У них разные QWaitCondition, смотри код.


Название: Re: QThread - синхронизация с главным потоком.
Отправлено: Winstrol от Июль 16, 2009, 21:10
Ну и что? Что плохого в том что я явно указал Qt::QueuedConnection
help по Qt::ConnectionType
Цитировать
Покажи решение лучше!
help по Qt::ConnectionType
Цитировать
У них разные QWaitCondition, смотри код.
Извиняюсь, не посмотрел. Жесть! Чистая и незамутненная.


Название: Re: QThread - синхронизация с главным потоком.
Отправлено: spectre71 от Июль 16, 2009, 21:49
Ну и что? Что плохого в том что я явно указал Qt::QueuedConnection
help по Qt::ConnectionType
Цитировать
Покажи решение лучше!
help по Qt::ConnectionType
Цитировать
У них разные QWaitCondition, смотри код.
Извиняюсь, не посмотрел. Жесть! Чистая и незамутненная.
help по Qt::ConnectionType
???


Название: Re: QThread - синхронизация с главным потоком.
Отправлено: Winstrol от Июль 16, 2009, 22:15
Для тех, у кого устаревший хелп, правильный ответ:
QMetaObject::invokeMethod(&...,"addWidget",Qt::BlockingQueuedConnection);   
или
emit addWidget();


Название: Re: QThread - синхронизация с главным потоком.
Отправлено: spectre71 от Июль 16, 2009, 23:22
Для тех, у кого устаревший хелп, правильный ответ:
QMetaObject::invokeMethod(&...,"addWidget",Qt::BlockingQueuedConnection);   
или
emit addWidget();
Qt::BlockingQueuedConnection - возможно, только не совсем понятно что значит:
Цитировать
Same as QueuedConnection, except that the current thread blocks until the slot has been delivered.
delivered - "доставлен" или "освобожден" - совершенно разный смысл, и понимай как хочешь! Если именно освобожден, то можно обойтись и без QMutex и QWaitCondition.
А QMetaObject::invokeMethod - не интересно, покольку задача выполнить любой метод/методы (любого объекта  или обычный метод) из дочернего потока в контексте главного.


Название: Re: QThread - синхронизация с главным потоком.
Отправлено: ритт от Июль 16, 2009, 23:31
Для тех, у кого устаревший хелп, правильный ответ:
QMetaObject::invokeMethod(&...,"addWidget",Qt::BlockingQueuedConnection);   
или
emit addWidget();
Qt::BlockingQueuedConnection - возможно, только не совсем понятно что значит:
Цитировать
Same as QueuedConnection, except that the current thread blocks until the slot has been delivered.
delivered - "доставлен" или "освобожден" - совершенно разный смысл, и понимай как хочешь! Если именно освобожден, то можно обойтись и без QMutex и QWaitCondition.
доставлен. для блокировки потока используется мутекс (если правильно помню). пока ивент не будет доставлен, мутекс не разблокируется.

А QMetaObject::invokeMethod - не интересно, покольку задача выполнить любой метод/методы (любого объекта  или обычный метод) из дочернего потока в контексте главного.
А QMetaObject::invokeMethod именно это и делает.


Название: Re: QThread - синхронизация с главным потоком.
Отправлено: spectre71 от Июль 17, 2009, 09:36
доставлен. для блокировки потока используется мутекс (если правильно помню). пока ивент не будет доставлен, мутекс не разблокируется.
Я правильно тебя понял, "доставлен" - момент непосредственно перед вызовом слота.

А QMetaObject::invokeMethod именно это и делает.
Согласен, но в данном случае, что через сигнал, что через него - особой разницы нет.


Название: Re: QThread - синхронизация с главным потоком.
Отправлено: ритт от Июль 17, 2009, 10:11
сигнализируя, ты не можешь ожидать момент доставки - поведение зависит целиком от connect
в случае же с invokeMethod ты сам можешь определять как будет вести себя конкретный клочок кода - в одном месте можно дожидаться доставки, в другом - нет...вдобавок для использования не требуется макрос Q_OBJECT и для ряда случаев сигналы вообще.


Название: Re: QThread - синхронизация с главным потоком.
Отправлено: Winstrol от Июль 17, 2009, 10:40
доставлен. для блокировки потока используется мутекс (если правильно помню). пока ивент не будет доставлен, мутекс не разблокируется.
Я правильно тебя понял, "доставлен" - момент непосредственно перед вызовом слота.
Нет.

Цитировать
void SbThread::wake (void) {
  Wait.wakeOne();
}
Может получить управление до wait


Название: Re: QThread - синхронизация с главным потоком.
Отправлено: spectre71 от Июль 17, 2009, 17:17
Может получить управление до wait

Если я привильно понял(QWaitCondition::wait)
Цитировать
This function is provided to allow the atomic transition from the locked state to the wait state.

Тогда так
Код
C++ (Qt)
void SbThread::wake (void) {
 Mutex.lock();
 Wait.wakeOne();
 Mutex.unlock();
}
 
void SbThread::doSynchronize(SbThreadSync* s) {
 Mutex.lock();
 emit signalSynchronize(s);
 Wait.wait(&Mutex);
 Mutex.unlock();
}