Скачиваний:
100
Добавлен:
01.05.2014
Размер:
1.56 Mб
Скачать

Пример многопоточного приложения

Мы рассмотрели все свойства и методы объекта-наследника TThread. Теперь на конкретных примерах посмотрим, как все это можно применять.

Предположим, что вам надо создать какую-то тестирующую программу, в которой пользователю задается вопрос и он должен на него ответить за установленный интервал времени. Тогда желательно, чтобы, пока он что-то делает в приложении, работая над вопросом, ему постоянно высвечивалось бы оставшееся до окончательного ответа время. Попробуем промоделировать подобную задачу. Начните новый проект и разместите на форме метку и две кнопки. Метка (Label1) служит для отображения оставшегося времени. Кнопка "Пуск" (BBegin) имитирует задание вопроса. Кнопка "Ответ" (BResp) имитирует ответ пользователя.

Выполните команду "File/New/Other" и в открывшемся окне репозитария на странице "New" выберите пиктограмму "Thread Object". На вопрос об имени (Class Name) создаваемого класса, ответьте, например, MyTime.

В окне Редактора Кода вы увидите файл реализации модуля, который будет обеспечивать поток, связанный с отображением времени. Откройте заголовочный файл этого модуля с описанием введенного вами класса MyTime. В разделpublicкласса введите объявление двух переменных:

TDateTime T0, ТМах;

Переменная T0будет хранить момент времени, в который задан вопрос, а переменнаяТМахбудет содержать заданный для ответа интервал времени. Обе переменные имеют типTDateTime, используемый в C++Builder для хранения времени и дат. ЗначениеТМахможно задать в конструкторе введенного вами классаMyTime:

__fastcall MyTime::MyTime(bool CreateSuspended)

: TThread(CreateSuspended)

{

TMax = EncodeTime (0, 0, 10, 0);

}

Единственный оператор этого конструктора формирует значение ТМахс помощью функцииEncodeTime, задавая интервал времени равным 10 секунд (третий параметр функции).

Сохраните проект, чтобы можно было без проблем ввести в коды ссылки модулей друг на друга. Назовите при сохранении первый (главный) модуль - UTime1, а второй - UTime2.

Формируемый нами поток должен будет отображать в метке Label1главной формыForm1время, оставшееся до окончания ответа. Поэтому прежде всего надо ввести ссылку в модулеUTime2на модульUTime1. Делается это или вставкой вручную в файлUTime2директивы препроцессора

#include "UTime1.h"

или командой "File/Include Unit Hdr".

Аналогично надо сослаться из файла UTime1на файлUTime2, поскольку из главной формы надо будет запускать и останавливать выполнение потока.

Так как поток будет работать с меткой Label1, являющейся компонентом VCL, надо, как указывалось в предыдущем разделе, использовать методSynchronizeи оформить операторы, работающие с меткой, в виде отдельной функции. Введите в разделprivateописания класса потока объявление этой функцииNewCaption:

void __fastcall NewCaption();

Реализация функции NewCaptionможет состоять всего из одного оператора:

void __fastcall MyTime::MewCaption()

{

Form1->Label1->Caption = TimeToStr (ТМах - (Now () - T0));

)

Аналогично надо оформить в виде отдельной функции отображение результатов тестирования. Для этого введите в раздел privateописания класса потока объявление еще одной функции -Result:

void __fastcall Result ();

Ее реализация может быть следующей:

void __fastcall MyTime::Result ()

{

if (Terminated) ShowMessage ("Ответ получен своевременно");

else ShowMessage ("Время на ответ исчерпано");

}

Тогда реализация функции Executeможет выглядеть следующим образом:

void __fastcall MyTime::Execute ()

{

Т0 = Now ();

do

{

Synchronize (NewCaption);

}

while (!Terminated && ((double) (TMax - (Now () - T0}) >= 0));

Synchronize(Result);

}

Этот код сначала запоминает в Т0текущий момент времени, определяемый функциейNow(), Затем выполняется циклdo...whileдо тех пор, пока или выполнение не будет прервано ответом пользователя (при ответе, как увидим позднее, значение свойстваTerminatedустановится вtrue), или не истечет отведенное на ответ время (разностьNow() - T0станет больше или равной заданному значениюТМах). В тело цикла помещен вызов с помощьюSynchronizeфункцииResult, которая выдает сообщение, фиксирующее получение ответа вовремя или окончание отведенного на ответ времени.

Теперь вернитесь к основному модулю. Введите в файл его реализации глобальную переменную:

MyTime *Thread;

Эта переменная - объект нити Threadсозданного вами классаMyTime. В обработчик щелчка на кнопкеBBegin(начало отсчета времени) вставьте код:

Thread = new MyTime (false);

Thread->FreeOnTerminate = true;

Первый оператор этого кода создает объект нити, передавая в него параметр false, что означает немедленное начало выполнения нити. Третий оператор задает значение свойстваFreeOnTcrrainate, обеспечивающее разрушение объекта нити после окончания ее выполнения.

В обработчик щелчка на кнопке BResp(ответ пользователя) вставьте оператор:

Thread->Terminate ();

Программирование закончено. Приведем в заключение, чтобы не возникло какой-то путаницы, основные коды файлов, исключая стандартные директивы компилятора:

// файл UTime2.h:

class MyTime : public TThread

{

private:

void __fastcall HewCaption ();

void __fastcall Result ();

protected:

void __fastcall Execute ();

public:

__fastcall MyTime (bool CreateSuspended);

TDateTime T0, TMax;

};

// файл UTime2.cpp:

#include "UTime1.h"

__fastcall MyTime::MyTime (bool CreateSuspended)

: TThread (CreateSuspended)

{

TMax = EncodeTime (0, 0, 10, 0);

}

void __fastcall MyTime::HewCaption ()

{

Form1->Label1->Caption = TimeToStr (TMax - (Now () - T0));

}

void __fastcall MyTime::Result ()

{

if (Terminated) ShowMessage ("Ответ получен своевременно");

else ShowMessage ("Время на ответ исчерпано");

}

void __fastcall MyTime::Execute ()

{

T0 = Now ();

do

{

Synchronize (NewCaption);

}

while (!Terminated && ((double)(TMax - (Now () - T0)) >= 0));

Synchronize (Result);

}

// файл UTime1.cpp

#include "UTime2.h"

...

MyTime *Thread;

...

void __fastcall TForm1::BBeginClick (TObject *Sender)

{

Thread = new MyTime (false);

Thread->FreeOnTerminate = true;

}

void __fastcall TForm1::BRespClick (TObject *Sender)

{

Thread->Terminate ();

}

Можете компилировать приложение, выполнять его и тестировать.

Теперь давайте усложним приложение, чтобы в нем было две нити, причем одна ждала бы другую. Введем нить, которая подводит итоги тестирования и в зависимости от полученного результат отображает сообщение, прошел или не прошел пользователь тестирование. Измените в модуле UTime2 функцию Resultследующим образом:

void __fastcall MyTime::Result ()

{

if (Terminated)

{

ShowMessage ("Ответ получен своевременно");

ReturnValue = 0;

}

else

{

ShowMessage ("Время на ответ исчерпано");

ReturnValue = 1;

}

}

В этой функции код возврата - свойство ReturnValueзадается равным нулю в случае досрочного ответа и равным 1 в противном случае.

Выполните опять команду "File New Other" и в окне репозитария на странице New выберите опять пиктограмму "Thread Object". На вопрос об имени создаваемого класса ответьте "TResult". Тем самым вы создали в приложении еще один класс нити. Сохраните модуль под именем Time3.

Введите в разделе privateобъявления класса этого модуля функциюResult2 и целую переменную Res:

class TResult : public TThread

{

private:

void __fastcall TResult::Result2();

int Res;

protected:

void __fastcall Execute();

public:

__fastcall TResult (bool CreateSuspended);

};

Переменная Resбудет хранить код возврата потокаThread, а реализация функцииResult2, обеспечивающей отображение результатов тестирования, может выглядеть следующим образом:

void __fastcall TResult::Result2 ()

{

if (Res == 0) ShowMessage ("Вы успешно прошли тестирование");

else ShowMessage ("Вы не прошли тестирование");

}

Функцию ExecuteклассаTResultзапишите так:

void __fastcall TResult::Execute ()

{

Res = Thread->WaitFor ();

Synchronize (Result2);

delete Thread;

}

Первый оператор функции обеспечивает методом WaitForожидание окончания нитиThreadи заносит ее код возврата в переменнуюRes. ФункцияResult2, вызываемая с помощьюSynchronize, анализирует значениеResи выдает соответствующее сообщение. Чтобы можно было анализировать код возвратаThread, объект этой нити не должен разрушаться при завершении выполнения. Поэтому его свойствоFreeOnTerminateдолжно быть равноfalse(это значение оно имеет по умолчанию). Но тогда надо принять меры к явному разрушению объекта. Поэтому в приведенный выше код добавлен операторdelete Thread.

Чтобы в модуле Time3 в приведенных операторах можно было обращаться к объекту Thread, объявленному в модуле Time1, надо повторить в Time3 объявлениеThread, снабдив его спецификациейextern:

extern MyTime *Thread;

А поскольку в этом объявлении используется класс MyTime, объявленный в заголовочном файле UTime2.h, надо ввести директиву, ссылающуюся на этот модуль:

#include "UTime2.h"

Это все, что надо сделать в модуле Time3. А в модуль Time1 надо внести ссылку на Time3:

#include "UTime3.h"

объявить переменную типа TResult:

TResult *Result;

и изменить обработчик щелчка на кнопке BBegin:

Thread = new MyTime (false};

Result = new TResult (false);

Result->FreeOnTerminate = true;

В этом коде в отличие от предыдущего примера для объекта Threadоставляется значение по умолчанию свойстваFreeOnTermmate(false), создается объект второго потокаResultи устанавливается вtrueего свойствоFreeOnTerminate.

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

Теперь давайте построим более сложный пример с двумя нитями. В котором, в частности, можно будет увидеть влияние приоритетов на выполнение нитей. К каждой нити относится свой таймер (Timer1 и Timer2), своя метка (Label1 и Label2), в которую заносится число срабатываний таймера, свои кнопки, которые осуществляют соответственно пуск нити (Пуск — кнопки BRes1иBRes2) после паузы, вызываемой нажатием кнопок Пауза (BSuspend1иBSuspend2), и завершение выполнения (Останов — кнопкиBTerm1иBTerm2), вызывающее разрушение объекта нити. Кнопка Синхронно (BSync) сбрасывает счетчики обеих нитей и запускает их одновременно. При этом можно наблюдать различие в скорости работы, вызываемое различными приоритетами, выбираемыми пользователем в выпадающих спискахComboBox.

Так как обе нити функционируют одинаково, в данном случае достаточно описать только один класс нити. Выполните обычные процедуры по созданию модуля этого класса потока. Основные фрагменты его кода приведены ниже:

// файл Unit2.h:

class T1 : public TThread

{

private:

void __fastcall UpdateTimer();

protected:

void __fastcall Execute();

public:

___fastcall T1 (bool CreateSuspended);

TTimer *Timer;

};

// файл Unit2.срр:

void __fastcall T1::Execute()

{

do

{

Synchronize(UpdateTimer);

}

while (!Terminated);

}

void __fastcall T1::UpdateTimer()

{

Timer->Enabled = !Terminated;

}

В этом коде в класс вводится переменная Timer- таймер. ПроцедураExecute, вызывая процедуруUpdateTimer, задает свойство таймераEnabledравнымtrue, пока нить выполняется, и равнымfalseв момент окончания выполнения. Таким образом, таймер запускается при выполнении нити и останавливается при ее завершении.

Теперь рассмотрим главную форму. На ней у таймеров Timer1иTimer2в процессе проектирования задайтеEnabled=false. В спискиComboBoxзанесите строки

tpIdle

tpLowest

tpLower

tpNormal

tpHigher

tpHighest

tpTimeCritical

описывающие возможные значения приоритетов потоков.

Сошлитесь на модуль Unit2.h:

#include "UTime2.h"

и введите глобальные переменные:

int i1 = 0, i2 = 0;

T1 *Thread1, *Thread2;

bool Term1, Term2;

Переменные i1иi2- счетчики, по которым можно судить о скорости выполнения нитей. ПеременныеThread1иThread2- объекты потоков. А переменныеTerm1иTerm2будут показывать, завершен ли соответствующий поток.

Ниже приведен код функций главной формы:

void __fastcall TForm1::FormCreate(TObject *Sender)

{

ComboBox1->ItemIndex = 3;

ComboBox2->ItemIndex = 3;

Thread1 = new T1 (false); // Запускается сразу

Thread1->Timer = Timer1;

Thread1->FreeOnTerminate = true;

Term1 = false;

Thread2 = new T1 (true); // Сразу не запускается

Thread2->Timer = Timer2;

Thread2->FreeOnTerminate = true;

Term2 = false;

}

void __fastcall TForm1::Timer1Timer (TObject *Sender)

{

i1++;

Label1->Caption = i1;

Timer1->Enabled = false;

}

void __fastcall TForm1::Timer2Timer (TObject *Sender)

{

i2++;

Label2->Caption = i2;

Timer2->Enabled = false;

}

void __fastcall TForm1::BRes1Click (TObject *Sender)

{

if (!Term1) Thread1->Resume ();

else ShowMessage ("Поток уже завершен");

)

void __fastcall TForm1::BRes2Click (TObject *Sender)

{

if (!Term2) Thread2->Resume ();

else ShowMessage ("Поток уже завершен");

}

void __fastcall TForm1::BTerm1Click (TObject *Sender)

{

Thread1->Terminate ();

Term1 = true;

}

void __fastcall TForm1::BTerm2Click (TObject *Sender)

{

Thread2->Terminate ();

Term2 = true;

}

void __fastcall TForm1::BSuspend1Click (TObject *Sender)

{

if (!Term1) Thread1->Suspend ();

else ShowMessage ("Поток уже завершен");

}

void __fastcall TForm1::BSuspend2Click (TObject *Sender)

{

if (!Term2) Thread2->Suspend ();

else ShowMessage ("Поток уже завершен");

}

void __fastcall TForm1::BSyncClick (TObject *Sender)

{

if (Term1 || Term2)

{

ShowMessage ("Поток уже завершен");

}

else

{

if (!Thread1->Suspended) Thread1->Suspend();

if (!Thread2->Suspended) Thread2->Suspend();

Timer1->Enabled = false;

Timer2->Enabled = false;

i1 = 0;

i2 = 0;

Thread1->Resume ();

Thread2->Resume ();

}

}

void __fastcall TForm1::ComboBox1Change (TObject *Sender)

{

if (Term1)

{

ShowMessage ("Поток уже завершен");

}

else

{

switch (ComboBox1->ItemIndex)

{

case 0: Thread1->Priority = tpIdle; break;

case 1: Thread1->Priority = tpLowest; break;

case 2: Thread1->Priority = tpLower; break;

case 3: Thread1->Priority = tpNormal; break;

case 4: Thread1->Priority = tpHigher; break;

case 5: Thread1->Priority = tpHighest; break;

case 6: Thread1->Priority = tpTimeCritical; break;

}

}

}

void __fastcall TForm1::ComboBox2Change (TObject *Sender)

{

if (Term2)

{

ShowMessage ("Поток уже завершен");

}

else

{

switch (ComboBox2->ItemIndex)

{

case 0: Thread2->Priority = tpIdle; break;

case 1: Thread2->Priority = tpLowest; break;

case 2: Thread2->Priority = tpLower; break;

case 3: Thread2->Priority = tpNormal; break;

case 4: Thread2->Priority = tpHigher; break;

case 5: Thread2->Priority = tpHighest; break;

case 6: Thread2->Priority = tpTimeCritical; break;

}

}

}

Процедура FormCreateсоздает объекты нитей и присваивает их свойствамTimerсоответствующие таймеры, расположенные на форме. Свойства нитейFreeOnTerminateзадаются равнымиtrue, чтобы по завершении выполнения объекты нитей автоматически разрушались. В приведенном коде для разнообразия первая нить начинает выполняться сразу после запуска приложения после создания объекта нити в обработчике событияOnCreateформы. Вторая нить создается с передачей в конструктор значенияtrue, что означает, что нить начнет выполняться только после вызова методаResume(вызывается при щелчке на кнопках Пуск — процедурыBRes1ClickиBRes2Click). Щелчки на кнопках Пауза (процедурыBSuspend1ClickиBSuspend2Click) приостанавливают выполнение соответствующей нити методамиSuspend. Щелчки на кнопках Останов (процедурыBTerm1ClickиBTerm2Click) завершают выполнение соответствующей нити. Щелчок на кнопке Синхронизация (процедураBSyncClick) приостанавливает выполнение обеих нитей (если они в данный момент не приостановлены) и таймеров, сбрасывает счетчикиilиi2на0и повторно запускает выполнение нитей. Изменение выбора в выпадающих списках (процедурыComboBox1ChangeиComboBox2Change) изменяет приоритеты нитей.

Для того чтобы после завершения того или иного потока не возникало ошибок при попытке выполнить какие-то его методы или задать свойства, в функциях введены проверки переменных Term1иTerm2, значения которых делаются равнымиtrueв процедурахBTerm1ClickиBTerm2Click.

Сохраните проект, скомпилируйте его и проверьте в различных режимах. Изменяя приоритеты нитей, вы сможете увидеть, что одинаковые вначале показания счетчиков начинают с течением времени расходиться. Нить с приоритетом, меньшим нормального, работает медленнее. Вы можете также увидеть. что многократная приостановка выполнения нити методом Suspendтребует такого же числа ее запуска методомResume, прежде чем она действительно начнет выполняться.