Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Qt: образец для правильной работы с потоками

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

Чего хочется

Тяготился я ко абсолютно явственным вещам:

  • применять классы С во каждой их красе — т. е. Дабы конструктор вызывался при создании потока и деструктор перед разрушением;
  • применять вероятности 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)		{ if (_thread) delete _thread; }
	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
{
private:
	Creator<T> *_creator;

protected:
	T*	_obj;
	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), _creator (0)
		{ }
	~ThreadedObject  (void)	{ if (_creator) delete _creator; }
	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 (new Creator<T>, 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 (); }	
};

Здесь стержневой способ — 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)		{ if (_thread) delete _thread; }			// создание иерархии деструкторов
	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); } };
	Creator<T> *_creator;		// класс создания объекта в потоке

protected:
	T*	_obj;						// объект
	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), _creator (0) {}	// конструктор
	~ThreadedObject  (void)	{ if (_creator) delete _creator; }		// деструктор
	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 (new Creator<T>, 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"

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB