Qt: шаблон для корректной работы с потоками

    Всем хабрапривет!
    Как-то понадобилось мне в Qt 5.1.1 для WinXP в VS2009 реализовать многопоточное приложение с интенсивным обменом сигналами. Взял я Шлее, вычитал у него, что нужно унаследовать класс от QThread и — вуаля, велком в многопоточность! На всякий случай заглянул в документацию Qt — там никто не возражал против наследования от QThread своего класса. Ну что же — порядок, сделано! Запускаю — вроде как работает, но как-то не так… Начинаю в режиме отладки отслеживать — а там творится черт знает что! То сигналы не выходят, то выходят, но как-то криво и из другого потока. Одним словом, полный бардак! Пришлось основательно по-google-ить и разобраться в теме (мне помогли статьи тут, здесь и там). В итоге я сделал шаблон класса на С++ (вернее, целую иерархию оных), что мне позволило в итоге писать (относительно) небольшой код класса, живущего в другом потоке, который работает правильно и стабильно.
    Upd: в комментариях мне подсказали более качественный подход — я его описал в новой статье.

    Чего хочется


    Стремился я ко вполне очевидным вещам:
    • использовать классы С++ во всей их красе — т. е. чтобы конструктор вызывался при создании потока и деструктор перед уничтожением;
    • использовать возможности Qt во всей его красе — т. е. сигнально-слотовые соединения, события и пр.;
    • при желании — контроль над процессом создания и работы — приоритет потока, слот о начале работы и сигнал об экстренном завершении;
    • минимум писанины и максимум понятности.

    Получилось у меня что-то вроде:

    class SomeJob: public QObject
    {
    	Q_OBJECT
    public:
    	SomeJob ()	{ /* ... */ }	// создание потока
    	~SomeJob ()	{ /* ... */ }	// удаление потока
    signals:
    	void finished ();	// поток закончил свою работу
    public slots:
    	void to_terminate ()	{ /* ... */ }	// экстренное завершение
    };
    
    ... 
    ThreadedObject<SomeJob> thr;	// объект-владелец потока
    thr.start ();	// создание потока с автозапуском конструктора
    


    Красотень!

    Как мы будем действовать


    В Qt 5.1 для наших целей предназначен низкоуровневый класс QThread. Про него сказано следующее: «класс QThread дает возможность в платформо-независимом виде управлять потоками». Замечательная книга у Шлее, но вот пример с потоками у него вышел очень сбивающим с толку: он предлагает наследовать класс QThread, переопределить метод run () и в нем выполнить работу. Это неплохо для задачи в стиле «запустили, выполнили функцию и завершили», но категорически неприемлемо для более сложных случаев.
    В общем, зря я его послушал прочитал, надо было сразу вникать в документацию. А в ней есть, между прочим, хороший пример. Он показывает правильный путь: функцию QObject::moveToThread (QThread *thread), которая переносит родственность (сходство? — англ. affinity) данного объекта и всех его предков потоку thread.
    Таким образом, в первом приближении решение задачи выглядит следующим образом:
    1. создание потока — класс QThread;
    2. создание объекта и перенос его в новый поток;
    3. установка сигнально-слотовых связей;
    4. запуск потока с заданным приоритетом.

    Вроде бы все хорошо, но — помните? — я хочу, чтобы конструктор создаваемого объекта выполнился в новом потоке. Для этого он должен быть запущен после запуска потока. Можно вначале создать объект, а потом поток. Но все, что будет создано конструктором объекта, будет размещено в стеке (куче) текущего потока, а не нового. Можно как-то аккуратно все это хозяйство перенести в новый поток и удалить в старом, но… проще вызвать конструктор уже в новом потоке. Так что вот нам проблема №1. Надо решать.
    Потом появилась проблема №2. Я создал симпатичный шаблон, который унаследован от QObject — это мне было нужно для сигнально-слотовых связей. И тут всплыла бяка: «MOC не позволяет использовать все возможности С++. Основная проблема в том, что шаблоны классов не могут иметь сигналы или слоты». #@&*!
    Впрочем, и эту тему я также преодолел.

    Я придумал следующие классы:
    1. ваш родной класс T;
    2. есть класс создания объекта — CreatorBase (потомок QObject). Он в слоте вызовом виртуального метода создает новый объект и его адрес передает сигналом;
    3. есть шаблонная реализация класса создателя — Creator<T> (потомок CreatorBase). Он реализует метод создания объекта заданного типа;
    4. есть класс ThreadedObjectBase (потомок QObject), который создает новый поток. Он получает объект-создатель CreatorBase и устанавливает необходимые сигнально-слотовые связи;
    5. пользователь использует шаблонный класс хранения объекта и потока ThreadedObject<T> (потомок ThreadedObjectBase). Он вызывает создание нового объекта и перегружает операторы * и ->, а также указатель типа создаваемого объекта;
    6. пользователь создает класс (он может быть потомком QObject), в котором по желанию реализует сигнал «класс закончил работу» и слот «прерывание работы», а также может задать отложенное удаление объекта.


    Последовательность действий получилась несложной:
    1. используется класс хранения объекта и потока ThreadedObject;
    2. он создает создатель объекта Creator и QThread для нового потока;
    3. создатель объекта переносится в новый поток;
    4. устанавливаются сигнально-слотовые связи;
    5. вновь созданный поток запускается с необходимым приоритетом;
    6. в созданном потоке создается пользовательский класс T;
    7. ThreadedObjectBase узнает об этом с помощью слота setObject (void *Obj), запоминает адрес объекта и оповещает об этом миру с помощью сигнала objectIsReady ();
    8. об успешном финале всех этих действий можно узнать у bool ThreadedObject<T>::objectIsCreated (void) const.


    Реализация


    Рассмотрим код созданных классов (чтобы оно все влезло в экран, я убрал комментарии).
    Создатели объектов:

    class CreatorBase: public QObject
    {
    Q_OBJECT
    	void	*_obj;
    protected:	virtual void *Allocation (void) = 0;
    public slots:	void allocate (void)		{ emit setObject (Allocation ()); }
    signals:	void setObject (void *Obj);
    };
    
    template <class T>
    class Creator: public CreatorBase
    {
    protected: void *Allocation (void) { return reinterpret_cast <void*> (new T); }
    };
    
    


    Тут все очевидно: базовый класс создателя CreatorBase имеет слот allocate (), который будет запущен в новом активном потоке. Он вызывает сигнал setObject (void *Obj), который передает адрес объекта, созданного в потомке void *Creator<T>::Allocation ().

    Базовый класс ThreadedObjectBase выглядит следующим образом:

    class ThreadedObjectBase: public QObject
    {
    	Q_OBJECT
    
    protected:
    	QThread	*_thread;
    
    	virtual void SetObjectPointer (void *Ptr) = 0;
    	ThreadedObjectBase (QObject *parent = 0): QObject (parent), _thread (0) {}
    
    	void starting (CreatorBase *Creator, QThread::Priority Priority = QThread::InheritPriority, bool ToDeleteLaterThread = true)
    	{
    		bool res;			
    		_thread = new QThread;
    		Creator->moveToThread (_thread);
    		res = connect (_thread, SIGNAL (started ()), Creator, SLOT (allocate ()));
    		Q_ASSERT_X (res, "connect", "connection is not established");
    		res = connect (Creator, SIGNAL (setObject (void*)), this, SLOT (setObject (void*)));
    		Q_ASSERT_X (res, "connect", "connection is not established");
    		if (ToDeleteLaterThread)
    		{
    			res = connect (_thread, SIGNAL (finished ()), _thread, SLOT (deleteLater ()));
    			Q_ASSERT_X (res, "connect", "connection is not established");
    		}
    		_thread->start (Priority);
    	}
    
    public:
    	virtual ~ThreadedObjectBase (void)		{  }
    	QThread *thread (void)	{ return _thread; }
    	const QThread *cthread (void) const	{ return _thread; }
    
    signals:
    	void objectIsReady (void);
    
    private slots:
    	void setObject (void *Obj)		{ SetObjectPointer (Obj); emit objectIsReady (); }
    };
    


    Основной метод тут — starting. Он создает поток с указанным приоритетом. При запуске поток _thread вызывает сигнал QThread::started (). Этот сигнал мы связываем со слотом CreatorBase::allocate (), который и создает новый объект. Тот, в свою очередь, вызывает сигнал CreatorBase::setObject (void *), который мы подхватываем слотом ThreadedObjectBase::setObject (void *Obj). Все, объект создан (о чем выдается сигнал ThreadedObjectBase::objectIsReady () ), указатель на него получен.

    Если пользователь желает установить отложенное удаление класса потока (что желательно), то устанавливается связь внутри _thread QThread::finished () -> QObject::deleteLater ().
    Также пользователь может установить имя сигнала (будет храниться в переменной _finished_signal). Этот сигнал вызывается создаваемым объектом по окончании своей работы. Аналогично слот из _terminate_slot будет вызываться сигналом прерывания работы потока (поток, впрочем, остановится не мгновенно; дождаться его окончания можно будет вызовом thread()->wait — см. QThread::wait).

    Ну и наконец видимый пользователю шаблонный класс:

    template <class T>
    class ThreadedObject: public ThreadedObjectBase
    {
    protected:
    	T*	_obj;
    	Creator<T> _creator;
    	const char	*_finished_signal;
    	const char	*_terminate_slot;
    	bool		_to_delete_later_object;
    
    	void SetObjectPointer (void *Ptr)
    	{
    		bool res;
    
    		_obj = reinterpret_cast <T*> (Ptr);
    
    		if (_finished_signal)
    		{
    			res = connect (_obj, _finished_signal, _thread, SLOT (quit ()));
    			Q_ASSERT_X (res, "connect", "connection is not established");
    		}
    
    		if (_terminate_slot)
    		{
    			res = connect (_thread, SIGNAL (finished ()), _obj, _terminate_slot);
    			Q_ASSERT_X (res, "connect", "connection is not established");
    		}
    		
    		if (_to_delete_later_object && _finished_signal)
    		{
    			res = connect (_obj, _finished_signal, _obj, SLOT (deleteLater ()));
    			Q_ASSERT_X (res, "connect", "connection is not established");
    		}
    	}
    
    public:
    	ThreadedObject (QObject *parent = 0): ThreadedObjectBase (parent), _obj (0)	{ }
    	~ThreadedObject  (void)	{  }
    	void start (const char *FinishedSignal = 0, const char *TerminateSlot = 0, QThread::Priority Priority = QThread::InheritPriority, bool ToDeleteLaterThread = true, bool ToDeleteLaterObject = true)
    	{
    		Creator<T> *creator = new Creator<T>;
    		_finished_signal = FinishedSignal;
    		_terminate_slot = TerminateSlot;
    		_to_delete_later_object = ToDeleteLaterObject;
    		starting (_creator, Priority, ToDeleteLaterThread);
    		delete creator;
    	}
    
    	bool objectIsCreated (void) const	{ return _obj != 0; }
    
    	T* ptr (void) 			{ return reinterpret_cast <T*> (_obj); }
    	const T* cptr (void) const	{ return reinterpret_cast <const T*> (_obj); }
    
    	// . перегрузки
    	operator T* (void)			{ return ptr (); }
    	T* operator -> (void)		{ return ptr (); }
    	operator const T* (void) const	{ return cptr (); }
    	const T* operator -> (void) const	{ return cptr (); }	
    };
    


    Тут основной метод — start, который запоминает имена сигналов и слотов, а также устанавливает отложенное удаление метода. Метод objectIsCreated () возвращает истину когда объект уже создан. Многочисленные перегрузки позволяют использовать ThreadedObject<T> как «умный» указатель.

    Вот простенький пример использования этих классов:

    
    ThreadedObject <Operation> _obj;
    QObject::connect (&_obj, SIGNAL (objectIsReady ()), this, SLOT (connectObject ()));
    _obj.start (SIGNAL (finished ()), SLOT (terminate ()), QThread::HighPriority);
    
    


    Снизу прилагается реальный пример — в основном потоке создается кнопка. В новом потоке создается переменная типа int, а также сигнал от таймера и событие по таймеру. Оба этих таймера уменьшают значение переменной int, по достижению нуля вызывается слот QCoreApplication::quit (). С другой стороны, закрытие приложения останавливает поток. Пример проверен в WinXP. Хотелось бы в комментариях услышать об успешных испытаниях в Linux, MacOS, Android и прочих поддерживаемых платформах.

    Пример + классы
    Файл ThreadedObject:

    // **
    // **  Базовый класс для создателя объекта
    // **
    
    class CreatorBase: public QObject
    {
    Q_OBJECT
    	void	*_obj;										// созданный объект
    protected:		virtual void *Allocation (void) = 0;	// тут будет создаваться объект
    public slots:	void allocate (void)		{ emit setObject (Allocation ()); }	// слот создания объекта
    signals:		void setObject (void *Obj);				// выдача созданного объекта
    };
    
    // **
    // **  Базовый класс для создания потока
    // **
    
    class ThreadedObjectBase: public QObject
    {
    	Q_OBJECT
    
    protected:
    	QThread	*_thread;				// поток
    
    	virtual void SetObjectPointer (void *Ptr) = 0;		// тут будет присваиваться объект
    	ThreadedObjectBase (QObject *parent = 0): QObject (parent), _thread (0) {}		// инициализация предка
    
    	void starting (CreatorBase *Creator, QThread::Priority Priority = QThread::InheritPriority, bool ToDeleteLaterThread = true)		// запуск нового потока
    	{
    		bool res;							// признак успешности установки сигналов-слотов
    		_thread = new QThread;		// создание потока
    		Creator->moveToThread (_thread);		// перенос _creator в поток
    		res = connect (_thread, SIGNAL (started ()), Creator, SLOT (allocate ()));				Q_ASSERT_X (res, "connect", "connection is not established");	// запуск потока _thread вызывает создание объекта Creator-ом
    		res = connect (Creator, SIGNAL (setObject (void*)), this, SLOT (setObject (void*)));	Q_ASSERT_X (res, "connect", "connection is not established");	// Creat-ор выдает адрес объекта
    		if (ToDeleteLaterThread)			// отложенное удаление thread?
    		{	res = connect (_thread, SIGNAL (finished ()), _thread, SLOT (deleteLater ()));		Q_ASSERT_X (res, "connect", "connection is not established");	}	// завершение потока _thread вызывает отложенное удаление объекта
    		_thread->start (Priority);			// установка приоритета
    	}
    
    public:
    	// . управление
    	virtual ~ThreadedObjectBase (void)		{  }			// создание иерархии деструкторов
    	QThread *thread (void)	{ return _thread; }				// поток, владеющий объектом
    
    	// . состояние
    	const QThread *cthread (void) const	{ return _thread; }		// поток, владеющий объектом
    
    signals:
    	void objectIsReady (void);				// сигнал "объект готов"
    
    private slots:
    	void setObject (void *Obj)				{ SetObjectPointer (Obj); emit objectIsReady (); }		// получение адреса объекта
    };	// class ThreadedObjectBase
    
    // **
    // **  Создание потока
    // **
    
    template <class T>
    class ThreadedObject: public ThreadedObjectBase
    {
    private:
    	template <class T> class Creator: public CreatorBase		// создатель объекта в потоке
    	{ protected: void *Allocation (void) { return reinterpret_cast <void*> (new T); } };
    
    protected:
    	T*	_obj;						// объект
    	Creator<T>	_creator;			// создатель потока
    	const char	*_finished_signal;			// сигнал "окончание работы объекта"
    	const char	*_terminate_slot;			// слот "остановка работы"
    	bool		_to_delete_later_object;	// установить "отложенне удаление объекта?
    
    	void SetObjectPointer (void *Ptr)		// установка связей объекта
    	{
    		bool res;							// признак успешности установки сигналов-слотов
    
    		_obj = reinterpret_cast <T*> (Ptr);	// установка указателя на объект
    
    		if (_finished_signal)				// установить сигнал "окончание работы объекта"?
    		{	res = connect (_obj, _finished_signal, _thread, SLOT (quit ()));			Q_ASSERT_X (res, "connect", "connection is not established");	}	// по окончанию работы объекта поток будет завершен
    		if (_terminate_slot)				// установить слот "остановка работы"?
    		{	res = connect (_thread, SIGNAL (finished ()), _obj, _terminate_slot);	Q_ASSERT_X (res, "connect", "connection is not established");	}	// перед остановкой потока будет вызван слот объекта "остановка работы"
    		if (_to_delete_later_object && _finished_signal)	// установить отложенное удаление объекта?
    		{	res = connect (_obj, _finished_signal, _obj, SLOT (deleteLater ()));	Q_ASSERT_X (res, "connect", "connection is not established");	}	// по окончанию работы объекта будет установлено отложенное удаление
    	}
    
    public:
    	// . управление
    	ThreadedObject (QObject *parent = 0): ThreadedObjectBase (parent), _obj (0) {}	// конструктор
    	~ThreadedObject  (void)	{  }				// деструктор
    	void start (const char *FinishedSignal = 0, const char *TerminateSlot = 0, QThread::Priority Priority = QThread::InheritPriority, bool ToDeleteLaterThread = true, bool ToDeleteLaterObject = true)		// запуск нового потока
    	{
    		_finished_signal = FinishedSignal;		// запоминание имени сигнала "окончание работы объекта"
    		_terminate_slot = TerminateSlot;		// запоминание имени слота "остановка работы"
    		_to_delete_later_object = ToDeleteLaterObject;	// запоминание установки отложенного удаление объекта
    		starting (_creator, Priority, ToDeleteLaterThread);	// создание объекта
    	}
    
    	// . состояние
    	bool objectIsCreated (void) const	{ return _obj != 0; }							// объект готов к работе?
    
    	T* ptr (void) 				{ return reinterpret_cast <T*> (_obj); }			// указатель на объект
    	const T* cptr (void) const	{ return reinterpret_cast <const T*> (_obj); }		// указатель на константный объект
    
    	// . перегрузки
    	operator T* (void)					{ return ptr (); }		// указатель на объект
    	T* operator -> (void)				{ return ptr (); }		// указатель на объект
    	operator const T* (void) const		{ return cptr (); }		// указатель на константный объект
    	const T* operator -> (void) const	{ return cptr (); }		// указатель на константный объект
    };	// class ThreadedObject
    
    


    Файл main.cpp:

    
    #include <QtGui>
    #include <QtWidgets>
    #include <QtCore>
     
    #include "ThreadedObject.h"
    
    // **
    // **  Выполнение операции
    // **
    
    class Operation: public QObject
    {
    	Q_OBJECT
    
    	int		*Int;		// некоторая динамическая переменная
    	QTimer	_tmr;		// таймер
    	int		_int_timer;	// внутренний таймер
    
    public:
    	Operation (void)	{ Int = new int (5); }			// некоторый конструктор
    	~Operation (void)	{ if (Int) delete Int; }		// некоторый деструктор
    
    signals:
        void addText(const QString &txt);		// сигнал "добавление текста"
    	void finished ();						// сигнал "остановка работы"
    
    public slots:
    	void terminate ()			// досрочная остановка
    	{
    		killTimer (_int_timer);		// остановка внутреннего таймера
    		_tmr.stop ();				// остановка внешенго таймера
    		delete Int;					// удаление переменной
    		Int = 0;					// признак завершения работы
    		emit finished ();			// сигнал завергения работы
    	}
        void doAction (void)		// некоторое действие
        {
    		bool res;
    		emit addText (QString ("- %1 -"). arg (*Int));
    		res = QObject::connect (&_tmr, &QTimer::timeout, this, &Operation::timeout);	Q_ASSERT_X (res, "connect", "connection is not established");	// связывание внешнего таймера
    		_tmr.start (2000);			// запуск внешнего таймера
    		thread()->sleep (1);		// выжидание 1 сек...
    		timeout ();					// ... выдача состояния ...
    		startTimer (2000);			// ... и установка внутреннего таймера
        }
    protected:
    	void timerEvent (QTimerEvent *ev)	{ timeout (); }		// внутренний таймер
    
    private slots:
    	void timeout (void)
    	{
    		if (!Int || !*Int)									// поток закрывается?
    			return;											// ... выход
    		--*Int;												// уменьшение счетчика
    		emit addText (QString ("- %1 -"). arg (*Int));		// выдача значения
    
    		if (!Int || !*Int)									// таймер закрыт?
    			emit finished ();								// ... выход
    
    	}
    
    };
    
    // **
    // **  Объект, взаимодействующий с потоком
    // **
    
    class App: public QObject
    {
    	Q_OBJECT
    
    	ThreadedObject <Operation>	_obj;		// объект-поток
    	QPushButton _btn;						// кнопка
    
    protected:
    	void timerEvent (QTimerEvent *ev)
    	{
    		bool res;							// признак успешности установки сигналов-слотов
    		killTimer (ev->timerId ());			// остановка таймера
    		res = QObject::connect (&_obj, SIGNAL (objectIsReady ()), this, SLOT (connectObject ()));		Q_ASSERT_X (res, "connect", "connection is not established");	// установка связей с объектом
    		_obj.start (SIGNAL (finished ()), SLOT (terminate ()), QThread::HighPriority);					// запуск потока с высоким приоритетом
    	}
    
    private slots:
    	void setText (const QString &txt)		{ _btn.setText (txt); }		// установка надписи на кнопке
    	void connectObject (void)		// установка связей с объектом
    	{
    		bool res;					// признак успешности установки сигналов-слотов
    		res = QObject::connect (this, &App::finish, _obj, &Operation::terminate);				Q_ASSERT_X (res, "connect", "connection is not established");	// закрытие этого объекта хакрывает объект в потоке
    		res = QObject::connect (this, &App::startAction, _obj, &Operation::doAction);			Q_ASSERT_X (res, "connect", "connection is not established");	// установка сигнала запуска действия
    		res = QObject::connect (_obj, &Operation::finished, this, &App::finish);				Q_ASSERT_X (res, "connect", "connection is not established");	// конец операции завершает работу приложения
    		res = QObject::connect (_obj, &Operation::addText, this, &App::setText);				Q_ASSERT_X (res, "connect", "connection is not established");	// установка надписи на кнопку
    		res = QObject::connect (&_btn, &QPushButton::clicked, _obj, &Operation::terminate);		Q_ASSERT_X (res, "connect", "connection is not established");	// остановка работы потока
    
    		_btn.show ();				// вывод кнопки
    		emit startAction ();		// запуск действия
    	}
    
    public slots:
    	void terminate (void)			{ emit finish (); }		// завершение работы приложения
    
    signals:
    	void startAction (void);		// сигнал "запуск действия"
    	void finish (void);				// сигнал "завершение работы"
    };
    
    // **
    // **  Точка входа в программу
    // **
     
    int main (int argc, char **argv)
    {
        QApplication app (argc, argv);		// приложение
    	App a;								// объект
    	bool res;							// признак успешности операции
    
    	a.startTimer (0);					// вызов функции таймера объекта при включении цикла обработки сообщений
    	res = QObject::connect (&a, SIGNAL (finish ()), &app, SLOT (quit ()));				Q_ASSERT_X (res, "connect", "connection is not established");	// окончание работы объекта закрывает приложение
    	res = QObject::connect (&app, SIGNAL (lastWindowClosed ()), &a, SLOT (terminate ()));	Q_ASSERT_X (res, "connect", "connection is not established");	// окончание работы приложения закрывает объект
    
    	return app.exec();					// запуск цикла обработки сообщений
    }
    
    #include "main.moc"
    
    

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 17

      –4
      В этом комментарии я буду выводить все изменения в тексте
        0
        Обнаружил ошибку в коде. Исправил
          0
          Написал новую статью по итогам обсуждения тут. Всех желающих приглашаю к прочтению!
          –3
          Совершенно не понятно, зачем всё это может кому-то понадобиться в реальной жизни ;)
            +4
            > работает, но как-то не так… Начинаю в режиме отладки отслеживать — а там творится черт знает что! То сигналы не выходят, то выходят, но как-то криво и из другого потока

            Вообще непонятно почему у вас были проблемы.
              +6
              Честно говоря, что-то я не проникся вашей идеей.
              • Наследовние от QThread требуется не так уж и часто, а если оно действительно нужно, то нет проблем создать объект внутри перегруженного метода run (текущий тред будет правильный), проинициализировать там все и правильно воспользоваться QEventLoop. Интерфейс внутренного объекта придется обернуть вызовами QMetaObject::invokeMethod(m_internalProcessor, "methodName", Qt::QueuedConnection);, что обезопасит всех пользователей от вопросов о синхронизации.
              • moveToThread требуется возможно еще реже, например, когда вы хотите иметь много объектов и процессить ивенты для всех объектов только в одном треде.
              • А вот что действительно нужно, так это для повседневных задач допилить QRunnable, чтобы использовать QFuture. К сожалению, QtConcurrent выглядит заброшенным, или просто ему приоритет понизили до самого низкого.
                0
                Я для работы использую такую схему:
                1) Создаю класс, унаследованный от QThread;
                2) Вкладываю в нем указатель ptr на класс, который я собираюсь использовать;
                3)завожу переменную isStart типа bool, которая сигнализирует о инициализации класса в новом потоке.
                4) примерно так переопределяю метод run():
                void SomethingClass::run()
                {
                    ptr = new T;
                    isStart = true;
                    exec();
                }
                

                5) объявляю функцию, которая просто возвращает указатель ptr;

                Данный подход хорош для случаев, когда есть происходит какая то однотипная обработка, осуществляемая силами одного класса.
                Минусом же данного подхода является то, что валидность указателя ptr это головная боль программиста.
                  0
                  Наследовать QThread — плохая практика. Нужно создавать воркер и переносить его в QThread. habrahabr.ru/post/150274/
                    +1
                    Не вижу практических причин создавать нечто вне потока исполнения, что бы затем перенести это в него.
                    С точки зрения практичности и эстетичность пользоваться методом run() как функцией, содержащую в себе исключительно пользовательский код для исполнения, возможно только при отсутствии метода exec(). Если требуется обработчик событий, то логично использовать классы, однако, инициализировать их объекты вне потока, по сути до его создания, по меньшей мере требует лишнего кода. Однако, на вкус и цвет все фломастеры разные.
                    –1
                    Как-то вы перестарались. По-моему, нормальная тема — это делать вот так:
                    QThread *thread = new QThread(this):
                    Worker *job = new Worker;
                    job->moveToThread(thread):
                    thread->start();
                    job->do();
                    

                    У меня такое решение в 100% случаев нормально работает, а когда не подходит, то я чаще всего прихожу к тому, что следует юзать QtConcurrent.
                      +1
                      Спешу вас расстроить, но вызов job->do() запустить метод в текущем потоке, а не в потоке thread.
                      Я накидал небольшой код иллюстрирующий это
                      #include <QCoreApplication>
                      #include <QDebug>
                      #include <QThread>
                      
                      class QSleep : public QThread
                      {
                      public:
                          QSleep();
                          static void qmsleep(int msec) {QThread::msleep(msec);}
                      };
                      
                      class worker : public QObject
                      {
                      public:
                          worker() {}
                          void doIt();
                      
                      };
                      
                      void worker::doIt()
                      {
                          while(true)
                          {
                              qDebug() << "I'm sleep now in new thread";
                              QSleep::qmsleep(100);
                      
                          }
                      }
                      int main(int argc, char *argv[])
                      {
                          QCoreApplication a(argc, argv);
                          QThread* th = new QThread();
                          worker* petrovich = new worker;
                          petrovich->moveToThread(th);
                          th->start();
                          petrovich->doIt();
                          for(int i = 0 ; i < 100; i++)
                          {
                              qDebug() << "I'm sleep  in this thread";
                              QSleep::qmsleep(1000);
                          }
                      
                          return a.exec();
                      }
                      


                      Тестировал на 4.7.4 win 8.1
                      Отпишитесь если будут другие результаты.
                        –1
                        Если do() запускает внутри потока ивентлуп, то все будет окей. Никто же не будет в do() запихивать нагрузку :)
                      +1
                      Я к тому, что весь код в do() будет выполняться в вызывающем его потоке. Это происходит потому, что функция moveToThread() отвязывает объект от обработчика очереди событий текущего потока и передает его новому. Поэтому запуск функции exec() внутри функции do() вообще то некорректен, так как обработчик очереди событий просто перезапустится, а не привяжется к новому потоку.
                      Поэтому если требуется вызвать метод класса, надо делать его слотом. Вам просто везло, что по умолчанию run() вызывает exec() и поток имеет очередь событий.
                        +2
                        В журнале DrDobbs помню статью (к сожалению ссылку найти не могу), в которой показывались приемы работы с многопоточностью в Qt.
                        Одна из концепций такова (демонстрационный пример):
                        class MyThreadImpl
                            : public QObject
                        {
                            Q_OBJECT
                        public:
                            MyThreadImpl();
                        
                        public slots:
                            void doStuff()
                            {
                                // impl...
                                emit onSomeStuff();
                                // impl
                            }
                        
                        signals:
                            void onSomeStuff();
                        };
                        
                        class MyThread
                            : public QThread
                        {
                            Q_OBJECT
                        public:
                            void doStuff()
                            {
                                emit doStuffImpl();
                            }
                        
                        signals:
                        // external
                            void onSomeStuff();
                        
                        // internal
                            void onStuffImpl();
                        
                        private:
                        
                            void run()
                            {
                                MyThreadImpl threadImpl;
                        
                                // signal to thread
                                connect(this, SIGNAL(onStuffImpl()), &threadImpl, SLOT(doStuff()));
                        
                                // signal from thread 
                                connect(&threadImpl, SIGNAL(onSomeStuff()), this, SIGNAL(onSomeStuff()));
                        
                                exec();
                            }
                        };
                        

                        Со своей стороны нахожу данный подход одним из самых удачных для Qt и всем его рекомендую. Функции типа moveToThread считаю костылями, призванными исправлять существующие архитектурные косяки в проекте. Использовать их для новых решений ИМХО не стоит.
                        Аналогичный, более классический пример с использованием QEvent, который несколько более многословен, однако дает лучшую производительность.
                          0
                          Здесь как я понимаю получается следующие: MyThread используется как создатель нового потока, так и интерфейса для вложенного в него пользовательского класса. В принципе подход неплохой, за исключением двух Но:
                          1) так как объект QThread принадлежит потоку, в котором был создан, то сигнал придет сначала в него, а потом будет уже будет ретранслирован требуемому классу, то есть лишняя генерация сигнала. В принципе из-за того, что в одном потоке вызов сигнала приравнивается к непосредственному вызову соединенного с ним слота, издержками в большинстве случаев можно пренебречь.
                          2) Более существенная проблема состоит в том, что класс MyThread придется переписывать под каждый новый пользовательский класс, используемый в другом потоке.
                          Способ с QEvent довольно интересный, только я им подробно не занимался, поэтому сказать про него не могу.
                            0
                            1) Это да. Передача через event`ы эту проблему решает.
                            2) Я не считаю это проблемой, все равно интерфейс, определенный в MyThread будет привязан к конкретной задаче. Класс на практике называться будет соответственно этой задаче (а не MyThread). С другой стороны этот способ точно так же довольно просто поддаётся шаблонизации. Поэтому, если уже очень хочется, можно придумать нечто обобщённое на этой базе.

                            К слову сказать с более ранних версиях Qt event based межпоточное взаимодействие было основным способом. Т.к. во-первых поток не имел своей встроенной очереди сообщений, как в Qt4-Qt5, а во-вторых система сигналов не поддерживала межпоточные соединения. Приходилось делать свою очередь сообщений внутри run и проектировать систему событий для потока. Зато можно было передавать в основной поток события с помощью функции postEvent (собственно сейчас так тоже можно).
                            0
                            Согласен, так качественнее. Спасибо за идею!
                            Хотя я бы добавил startTimer(0), а в нем уже инициализацию нового объекта — тогда он будет работать с готовым циклом сообщений.

                          Only users with full accounts can post comments. Log in, please.