Каталог статей

Главная » Статьи » Мои статьи

Работа с COM-портом с помощью потоков a

Поток записи байтов

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

Ниже приведён шаблон потока записи в порт, поправки на TThread и WinAPI даны в соответствующих разделах.

//---------------------------------------------------------------------------

//главная функция потока, выполняет передачу байтов из буфера в COM-порт

{
 DWORD temp, signal; //temp - переменная-заглушка

 overlappedwr.hEvent = CreateEvent(NULL, true, true, NULL); //создать событие

 //записать байты в порт (перекрываемая операция!)
 WriteFile(COMport, bufwr, strlen(bufwr), &temp, &overlappedwr);

 signal = WaitForSingleObject(overlappedwr.hEvent, INFINITE); //приостановить поток, пока не завершится
 //перекрываемая операция WriteFile
 //если операция завершилась успешно, установить соответствующий флажок
 if((signal == WAIT_OBJECT_0) && (GetOverlappedResult(COMport, &overlappedwr, &temp, true))) fl = true;
 else fl = false;

}

//---------------------------------------------------------------------------

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

OVERLAPPED overlappedwr;

Её также необходимо объявить глобально.

Как и в потоке чтения, для асинхронной операции записи необходимо создать сигнальный объект-событие с помощью функции CreateEvent:

overlappedwr.hEvent = CreateEvent(NULL, true, true, NULL);

Параметры используются такие же: NULL, true, true, NULL.

Далее запускаем перекрываемую операцию записи функцией WriteFile:

WriteFile(COMport, bufwr, strlen(bufwr), &temp, &overlappedwr);

Здесь в функцию передаются следующие параметры:

COMport - дескриптор порта;

bufwr - указатель на передающий программный буфер, содержащий данные, которые нужно записать в порт;

strlen(bufwr) - количество байтов передаваемых данных, в данном случае - длина строки в буфере;

&temp - адрес переменной, в которую будет помещено число фактически записанных байтов;

&overlappedwr - адрес структуры OVERLAPPED, содержащей дескриптор сигнального объекта-события, используемого перекрываемой функцией WriteFile.

Чтобы поток не занимал процессорное время, ожидая, пока запущенная перекрываемая операция записи байтов в порт не завершится, его следует приостановить. Для этого также используется функция WaitForSingleObject:

signal = WaitForSingleObject(overlappedwr.hEvent, INFINITE);

Как только перекрываемая операция записи в порт завершится, сигнальный объект-событие с дескриптором overlappedwr.hEvent установится в сигнальное состояние и функция WaitForSingleObject активирует поток. При этом она должна вернуть значение WAIT_OBJECT_0, которое свидетельствует о том, что асинхронная перекрываемая операция завершилась. Любое другое значение, как и в случае с потоком чтения, будет свидетельствовать об ошибке. Если все-таки возвращено значение WAIT_OBJECT_0, то функцией GetOverlappedResult проверяем, успешно ли завершилась операция.

if((signal == WAIT_OBJECT_0)&&(GetOverlappedResult(COMport, &overlappedwr, &temp, true))) fl = true;
else fl = false;

Если успешно, то GetOverlappedResult вернёт true, тогда устанавливаем флажок fl в true. Если же операция завершилась неуспешно (WaitForSingleObject вернула значение, отличное от WAIT_OBJECT_0, либо GetOverlappedResult вернула false, либо оба случая сразу), флажок fl сбрасываем в false. По этому флажку потом можно будет определить, завершилась операция чтения успешно или нет.

На этом поток записи в порт завершается.

Создание потока

Для создания потоков можно использовать любой из двух способов:

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

2) создание потока с помощью функций WinAPI. В этом случае поток создаётся в виде функции. То есть при создании потока ему передаётся функция, которую он будет исполнять.

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

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

Создание потока с помощью класса TThread

Сначала рассмотрим создание потока чтения, а затем по аналогии создадим поток записи.

Поток чтения из порта

В программе C++ Builder это можно сделать следующим образом: открыть меню File->New->вкладка New->ThreadObject. Затем в появившейся форме нужно задать имя нового класса-потока: ReadThread.

Появится новый файл с названием Unit2.cpp (или другой порядковый номер в зависимости от количества модулей). Если наличие нескольких модулей приемлемо, то этот файл просто нужно включить в проект через #include.

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

1) Активировать вкладку с Unit2.cpp, если она еще не активирована.

2) Нажав на имя вкладки Unit2.cpp правой кнопкой мыши, выбрать пункт меню "Open Source/Header file" (Ctrl+F6). Затем скопировать из открывшегося файла Unit2.h следующее:

//---------------------------------------------------------------------------
class ReadThread : public TThread
{
private:
protected:
 void __fastcall Execute(); //главная функция потока
public:
 __fastcall ReadThread(bool CreateSuspended); //конструктор потока
};

//---------------------------------------------------------------------------

и добавить в главный модуль программы желательно вверху, там, где объявлены переменные и функции (то есть до того, как пользоваться потоком).

Это объявление класса ReadThread. В данном объявлении указывается защищённая функция Execute(), которая является основной исполняемой функцией класса потока, которая используется только этим классом и не видна извне (является закрытой). Открытая функция ReadThread(bool CreateSuspended) - это конструктор класса. О нём сказано ниже.

3) из файла Unit2.cpp скопировать следующее (поместить тоже до использования потоков):

//---------------------------------------------------------------------------
__fastcall ReadThread::ReadThread(bool CreateSuspended) //конструктор потока, по умолчанию - пустой
 : TThread(CreateSuspended)
{
}
//---------------------------------------------------------------------------

Это конструктор класса ReadThread. По умолчанию он является пустым, но при необходимости в него можно добавить какие-либо действия, которые будут выполняться при создании потока. (Создаваемый поток является объектом класса). Параметр CreateSuspended (создать в остановленном состоянии) определяет, будет ли запущен поток сразу после создания (CreateSuspended = false), либо только после вызова метода Resume() (если CreateSuspended = true).

//---------------------------------------------------------------------------
void __fastcall ReadThread::Execute()
{
 //здесь вставляется код, который будет исполняться потоком
}
//---------------------------------------------------------------------------

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

//---------------------------------------------------------------------------
void __fastcall ReadThread::Execute()
{
 OVERLAPPED over;
 COMSTAT curstat;
 DWORD btr, temp, temp, mask, signal; //переменная temp используется в качестве заглушки

 over.hEvent = CreateEvent(NULL, true, true, NULL); //создать событие; true,true - для асинхронных операций
 SetCommMask(comport, EV_RXCHAR); //маска = если принят байт
 while(!Terminated) //пока поток не будет прерван, выполняем цикл
 {WaitCommEvent(comport, &mask, &over); //ожидать события принятия байта
 signal = WaitForSingleObject(over.hEvent, INFINITE); //усыпить поток до прихода байта
 if(signal == WAIT_OBJECT_0) //если событие прихода байта произошло
 {if(GetOverlappedResult(comport, &over, &temp, true)) // проверяем, успешно ли завершилась
 // перекрываемая операция WaitCommEvent
 if((mask&EV_RXCHAR)!=0) //если произошло именно событие прихода байта
 {ClearCommError(comport, &temp, &curstat); //нужно заполнить структуру COMSTAT
 btr = curstat.cbInQue; //получить количество принятых байтов
 if(btr) //если в буфере порта есть непрочитанные байты
 ReadFile(comport, buf, btr, &temp, &over); //прочитать байты из порта в буфер программы
 }
 }
 }
 CloseHandle(over.hEvent); //закрыть объект-событие
}

//---------------------------------------------------------------------------

В случае использования потоков TThread в качестве условия в цикле ожидания и чтения байтов используется (!Terminated). Это означает, что данный цикл будет выполняться, пока поток не будет прерван с помощью метода Terminate(). Этот метод устанавливает свойство потока Terminated в true, таким образом сообщая потоку, что он должен завершиться как только это возможно. Проверяя этот флаг, поток может определить, когда он должен завершиться, и выполнить перед завершением какие-нибудь действия. В данном случае, получив запрос на завершение, поток выходит из цикла чтения байта (так как Terminated становится равен true) и освобождает дескриптор события hEvent в структуре типа OVERLAPPED, которая использовалась при асинхронных операциях. (Напомним, что этот объект событие был создан с помощью функции CreateEvent). После чего поток прекращает выполнение.

Использование метода Synchronize

В случае, когда поток делит графические компоненты и файлы с другими потоками, обращение к ним может привести к конфликту между потоками. Во избежание этих конфликтов используется метод Synchronize(TThreadMethod &Method), который использует для обращения к компонентам очередь сообщений главного потока. Он выполняет переданный в него метод в главном потоке (этот поток отвечает за работу с графическими компонентами), причём поток, вызвавший метод Synchronize, приостанавливается на время выполнения этого метода.

Чтобы обратиться к графическим компонентам или файлу, сначала нужно создать отдельный метод потока и объявить его в объявлении класса:

//---------------------------------------------------------------------------

class ReadThread : public TThread
{
private:
protected:
 void __fastcall Execute();
 void __fastcall Printing(); //добавлено вручную для использования в Synchronize
public:
 __fastcall ReadThread(bool CreateSuspended);
};

//---------------------------------------------------------------------------

И написать соответствующую функцию. Например:

//---------------------------------------------------------------------------

//выводим принятые байты на экран и в файл (если включено)
void __fastcall ReadThread::Printing()
{
 //выводим принятую строку в Memo
 Form1->Memo1->Lines->Add((char*)bufrd);

 //выводим счётчик в строке состояния
 Form1->StatusBar1->Panels->Items[2]->Text = "Всего принято " + IntToStr(counter) + " байт";

 if(Form1->CheckBox3->Checked == true) //если включен режим вывода в файл
 {
 write(handle, bufrd, strlen(bufrd)); //записать в файл данные из приёмного буфера
 }
 memset(bufrd, 0, BUFSIZE); //очистить буфер (чтобы данные не накладывались друг на друга)
}

//---------------------------------------------------------------------------

Эта функция выполняет вывод текущего состояния на форму и запись принимаемых данных в файл.

После этого в функции Execute() потока добавить метод Synchronize(функция) в месте, где требуется вызвать функцию, работающую с графическими компонентами или файлами. Например:

//---------------------------------------------------------------------------
void __fastcall ReadThread::Execute()
{

...

Synchronize(Printing);
}
//---------------------------------------------------------------------------
Как использовать поток

1) Класс - это тип данных, определяемый пользователем. Объект - конкретный экземпляр данного класса. Это аналогично типу данных (например, int) и переменной данного типа (например, i).

То есть перед тем, как можно будет создать и запустить поток, необходимо объявить объект класса, то есть некую переменную типа ReadThread, например:

ReadThread *reader; //объект потока ReadThread

Нужно сделать ее глобальной, чтобы иметь возможность доступа к ней из других функций. Сам класс ReadThread должен быть объявлен раньше.

2) Теперь нужно создать поток:

reader = new ReadThread(false); //создать и запустить поток чтения байтов
reader->FreeOnTerminate = true; //установить это свойство потока,
 //чтобы он автоматически уничтожался после завершения

В первой строке мы создаём объект класса ReadThread. Для этого вызывается конструктор потока ReadThread(), которому в качестве аргумента передаётся значение false. Это означает, что поток начинает работать сразу после создания (см. описание конструктора выше). Почему так сделано? Потому что в данной версии программы поток создаётся в функции открытия порта COMOpen(), то есть когда нужно начинать считывать байты (сразу после открытия порта).

Следует отметить, что потоки создаются только динамическим образом, то есть используя указатель (*reader) и оператор new (new ReadThread(false)).

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

То есть в целом создание потока будет выглядеть так:

ReadThread *reader; //объект потока ReadThread
reader = new ReadThread(false); //создать и запустить поток чтения байтов
reader->FreeOnTerminate = true; //установить это свойство потока,
 //чтобы он автоматически уничтожался после завершения

После того, как поток больше не нужен, его следует завершить, используя метод Terminate(). Как говорилось выше, этот метод устанавливает флаг потока Terminated в true, сообщая потоку, что он должен завершиться.

То есть, вызываем завершение потока таким образом:

if(reader)reader->Terminate();

Проверка if(reader) необходима, если её не делать, будут возникать ошибки (это проверено).

Уничтожение потока в первой версии программы производится в функции закрытия порта COMClose(). Таким образом, при открытии порта необходимо создавать поток заново, как и было реализовано в функции COMOpen() в этой версии программы (см. ниже).

Для обработки завершения потока можно определить обработчик события OnTerminate, который вызывается между окончанием выполнения потока (когда он выполнил return) и его разрушением. В этом обработчике можно свободно работать с графическими компонентами, так как он исполняется в контексте основного потока программы. То есть в этом обработчике можно определить некоторый код, который, например, будет освобождать объект потока или выводить на форму какое-то сообщение.

Полностью код потока чтения выглядит так:

//---------------------------------------------------------------------------

//поток для чтения последовательности байтов из COM-порта в буфер
class ReadThread : public TThread
{
 private:
 void __fastcall Printing(); //вывод принятых байтов на экран и в файл
 protected:
 void __fastcall Execute(); //основная функция потока
 public:
 __fastcall ReadThread(bool CreateSuspended); //конструктор потока
};

//---------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//............................... поток ReadThead .............................
//-----------------------------------------------------------------------------

ReadThread *reader; //объект потока ReadThread

//---------------------------------------------------------------------------

//конструктор потока ReadThread, по умолчанию пустой
__fastcall ReadThread::ReadThread(bool CreateSuspended) : TThread(CreateSuspended)
{}

//---------------------------------------------------------------------------

//главная функция потока, реализует приём байтов из COM-порта
void __fastcall ReadThread::Execute()
{
 COMSTAT comstat; //структура текущего состояния порта, в данной программе используется
 //для определения количества принятых в порт байтов
 DWORD btr, temp, mask, signal; //переменная temp используется в качестве заглушки

 //создать сигнальный объект-событие для асинхронных операций
 overlapped.hEvent = CreateEvent(NULL, true, true, NULL);

 SetCommMask(COMport, EV_RXCHAR); // установить маску на срабатывание по событию
 // приёма байта в порт
 while(!Terminated) //пока поток не будет прерван, выполняем цикл
 {
 WaitCommEvent(COMport, &mask, &overlapped); // ожидать события приёма байта
 // (это и есть перекрываемая операция)
 signal = WaitForSingleObject(overlapped.hEvent, INFINITE); //приостановить поток до прихода байта
 if(signal == WAIT_OBJECT_0) //если событие прихода байта произошло
 {
 if(GetOverlappedResult(COMport, &overlapped, &temp, true)) // проверяем, успешно ли завершилась
 // перекрываемая операция WaitCommEvent
 if((mask & EV_RXCHAR)!=0) //если произошло именно событие прихода байта
 {
 ClearCommError(COMport, &temp, &comstat); //нужно заполнить структуру COMSTAT
 btr = comstat.cbInQue; //и получить из неё количество принятых байтов
 if(btr) //если действительно есть байты для чтения
 {
 ReadFile(COMport, bufrd, btr, &temp, &overlapped); //прочитать байты из порта в буфер программы
 counter+=btr; //увеличиваем счётчик байтов
 Synchronize(Printing); //вызываем функцию для вывода данных на экран и в файл
 }
 }
 }
 }
 CloseHandle(overlapped.hEvent); //перед выходом из потока закрыть объект-событие
}

//---------------------------------------------------------------------------

//выводим принятые байты на экран и в файл (если включено)
void __fastcall ReadThread::Printing()
{
 //выводим принятую строку в Memo
 Form1->Memo1->Lines->Add((char*)bufrd);

 /выводим счётчик в строке состояния
 Form1->StatusBar1->Panels->Items[2]->Text = "Всего принято " + IntToStr(counter) + " байт";

 if(Form1->CheckBox3->Checked == true) //если включен режим вывода в файл
 {
 write(handle, bufrd, strlen(bufrd)); //записать в файл данные из приёмного буфера
 }
 memset(bufrd, 0, BUFSIZE); //очистить буфер (чтобы данные не накладывались друг на друга)
}

//---------------------------------------------------------------------------
Поток записи в порт

Поток записи в порт создаётся аналогично потоку чтения.

Точно так же как и для потока чтения, нужно создать объявление класса и для потока записи.

//---------------------------------------------------------------------------

//поток для записи последовательности байтов из буфера в COM-порт
class WriteThread : public TThread
{
private:
 void __fastcall Printing(); //вывод состояния на экран
protected:
 void __fastcall Execute(); //основная функция потока
public:
 __fastcall WriteThread(bool CreateSuspended); //конструктор потока
};

//---------------------------------------------------------------------------

Здесь точно так же присутствуют конструктор класса потока записи WriteThread(bool CreateSuspended), главная функция потока Execute() и дополнительная функция Printing(), которая используется для вывода результатов операции в строке состояния.

Основная функция потока Execute() выполняет передачу данных в порт:

//---------------------------------------------------------------------------

//главная функция потока, выполняет передачу байтов из буфера в COM-порт
void __fastcall WriteThread::Execute()
{
 DWORD temp, signal; //temp - переменная-заглушка

 overlappedwr.hEvent = CreateEvent(NULL, true, true, NULL); //создать событие

 //записать байты в порт (перекрываемая операция!)
 WriteFile(COMport, bufwr, strlen(bufwr), &temp, &overlappedwr);

 //приостановить поток, пока не завершится перекрываемая операция WriteFile
 signal = WaitForSingleObject(overlappedwr.hEvent, INFINITE);

 //если операция завершилась успешно, установить соответствующий флажок
 if((signal == WAIT_OBJECT_0) && (GetOverlappedResult(COMport, &overlappedwr, &temp, true))) fl = true;
 else fl = false;

 Synchronize(Printing); //вывести состояние операции в строке состояния
 CloseHandle(overlappedwr.hEvent); //перед выходом из потока закрыть объект-событие
}

//---------------------------------------------------------------------------

Функция Printing() выполняет вывод состояния операции в строке состояния:

//---------------------------------------------------------------------------

//вывод состояния передачи данных на экран
void __fastcall WriteThread::Printing()
{
 if(!fl) //проверяем состояние флажка
 {
 Form1->StatusBar1->Panels->Items[0]->Text = "Ошибка передачи";
 return;
 }
 Form1->StatusBar1->Panels->Items[0]->Text = "Передача прошла успешно";
}

//---------------------------------------------------------------------------

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

Для этого выполняется следующее:

1) после объявления класса потока записи объявляется объект потока записи:

WriteThread *writer; //объект потока WriteThread

Этот объект также должен быть объявлен глобально.

2) создать объект класса с помощью оператора new:

writer = new WriteThread(false); //активировать поток записи данных в порт
writer->FreeOnTerminate = true; //установить это свойство,
 //чтобы поток автоматически уничтожался после завершения

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

В программе поток создаётся при необходимости отправки данных - в обработчике нажатия кнопки "Передать":

//---------------------------------------------------------------------------

//кнопка "Передать"
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 memset(bufwr,0,BUFSIZE); //очистить программный передающий буфер,
 //чтобы данные не накладывались друг на друга
 PurgeComm(COMport, PURGE_TXCLEAR); //очистить передающий буфер порта
 strcpy(bufwr,Form1->Edit1->Text.c_str());//занести в программный передающий буфер строку из Edit1

 writer = new WriteThread(false); //создать и активировать поток записи данных в порт
 writer->FreeOnTerminate = true; //установить это свойство,
 //чтобы поток автоматически уничтожался после завершения

}

//---------------------------------------------------------------------------

Когда поток больше не нужен, он также уничтожается вызовом метода Terminate():

//если поток записи работает, завершить его; проверка if(writer) обязательна, иначе возникают ошибки
if(writer)writer->Terminate();

А полностью код потока записи в порт выглядит так:

//---------------------------------------------------------------------------

//поток для записи последовательности байтов из буфера в COM-порт
class WriteThread : public TThread
{
private:
 void __fastcall Printing(); //вывод состояния на экран
protected:
 void __fastcall Execute(); //основная функция потока
public:
 __fastcall WriteThread(bool CreateSuspended); //конструктор потока
};

//---------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//............................... поток WriteThead ............................
//-----------------------------------------------------------------------------

WriteThread *writer; //объект потока WriteThread

//---------------------------------------------------------------------------

//конструктор потока WriteThread, по умолчанию пустой
__fastcall WriteThread::WriteThread(bool CreateSuspended) : TThread(CreateSuspended)
{}

//---------------------------------------------------------------------------

//главная функция потока, выполняет передачу байтов из буфера в COM-порт
void __fastcall WriteThread::Execute()
{
 DWORD temp, signal; //temp - переменная-заглушка

 overlappedwr.hEvent = CreateEvent(NULL, true, true, NULL); //создать событие

 //записать байты в порт (перекрываемая операция!)
 WriteFile(COMport, bufwr, strlen(bufwr), &temp, &overlappedwr);

 //приостановить поток, пока не завершится перекрываемая операция WriteFile
 signal = WaitForSingleObject(overlappedwr.hEvent, INFINITE);

 //если операция завершилась успешно, установить соответствующий флажок
 if((signal == WAIT_OBJECT_0) && (GetOverlappedResult(COMport, &overlappedwr, &temp, true))) fl = true;
 else fl = false;

 Synchronize(Printing); //вывести состояние операции в строке состояния
 CloseHandle(overlappedwr.hEvent); //перед выходом из потока закрыть объект-событие
}

//---------------------------------------------------------------------------

//вывод состояния передачи данных на экран
void __fastcall WriteThread::Printing()
{
 if(!fl) //проверяем состояние флажка
 {
 Form1->StatusBar1->Panels->Items[0]->Text = "Ошибка передачи";
 return;
 }
 Form1->StatusBar1->Panels->Items[0]->Text = "Передача прошла успешно";
}

//---------------------------------------------------------------------------
Использование методов Resume() и Suspend()

Вторая версия программы с использованием TThread демонстрирует использование методов Resume() и Suspend() для запуска и остановки потоков. В целом эта версия программы практически аналогична первой, все отличия связаны с использованием этих методов для управления потоком записи. Рассмотрим эти отличия:

1) В данном случае поток записи создаётся непосредственно в функции открытия порта COMOpen() после всех действий по открытию и инициализации порта (в конце функции), вместе с потоком чтения. При этом в конструктор потока чтения ReadThread(), как и в первой версии программы, передаётся параметр false, чтобы запустить поток чтения сразу после создания. А вот в конструктор потока записи WriteThread() передаётся параметр true, чтобы поток создался в остановленном состоянии. Будем запускать его по мере необходимости.

reader = new ReadThread(false); //создать и запустить поток чтения байтов
reader->FreeOnTerminate = true; //установить это свойство потока,
 //чтобы он автоматически уничтожался после завершения

writer = new WriteThread(true); //создать поток записи данных в порт
writer->FreeOnTerminate = true; //установить это свойство,
 //чтобы поток автоматически уничтожался после завершения

2) Главное отличие этой версии в том, что поток записи не разрушается после отправки данных, а существует в течение всего времени, пока открыт порт. Благодаря этому не нужно создавать новый поток, а затем разрушать его, каждый раз, когда требуется отправить данные. Вместо этого поток приостанавливается на то время, пока он не нужен, и активируется, когда необходимо передать данные.

Таким образом, поток записи writer будем включать, когда необходимо передать данные, то есть при нажатии кнопки "Передать". Для этого используется метод Resume() (переводится с английского как "возобновить"):

//кнопка "Передать"
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 memset(bufwr,0,BUFSIZE); //очистить программный передающий буфер,
 //чтобы данные не накладывались друг на друга
 PurgeComm(COMport, PURGE_TXCLEAR); //очистить передающий буфер порта
 strcpy(bufwr,Form1->Edit1->Text.c_str()); //занести в программный передающий буфер строку из Edit1
 writer->Resume(); //активировать поток записи в порт
}

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

Для этого построим код потока следующим образом:

//---------------------------------------------------------------------------

//главная функция потока, выполняет передачу байтов из буфера в COM-порт
void __fastcall WriteThread::Execute()
{
 DWORD temp, signal; //temp - переменная-заглушка

 overlappedwr.hEvent = CreateEvent(NULL, true, true, NULL); //создать событие

 while(!Terminated) //пока поток не будет завершён, выполнять цикл
 {
 //записать байты в порт (перекрываемая операция!)
 WriteFile(COMport, bufwr, strlen(bufwr), &temp, &overlappedwr);

 //приостановить поток, ппока не завершится перекрываемая операция WriteFile
 signal = WaitForSingleObject(overlappedwr.hEvent, INFINITE);

 //если операция завершилась успешно, установить соответствующий флажок
 if((signal == WAIT_OBJECT_0) && (GetOverlappedResult(COMport, &overlappedwr, &temp, true))) fl = true;
 else fl = false;

 Synchronize(Printing); //вывести состояние операции в строке состояния
 }
 CloseHandle(overlappedwr.hEvent); //перед выходом из потока закрыть объект-событие
}

//---------------------------------------------------------------------------

В данном случае используется бесконечный цикл while(!Terminated), который будет выполняться, пока потоку не будет передана команда завершения (с помощью метода потока Terminate()). Это сделано для того, чтобы поток сам по себе не завершился после отправки данных, как это было сделано в первой версии программы. В первой версии поток просто последовательно выполнял все команды и завершался, когда они заканчивались. И каждый раз при необходимости отправить данные создавался новый поток. В этой же версии нам нужно, чтобы поток существовал всё время, пока открыт порт. Поэтому в нём создаётся бесконечный цикл.

В цикле, как и в первой версии программы, выполняются операции по передаче данных в порт, после чего вызывается функция потока Printing для вывода результата операции в строке состояния (Synchronize(Printing)). Именно в этой функции мы и будем останавливать поток, так как если мы остановим его сразу после записи данных в порт, но перед вызовом функции Printing, то мы сможем увидеть результат операции в строке состояния только при следующем запуске потока, так как поток запустится с точки останова. Поэтому и делаем останов потока в функции Printing следующим образом (для этого используем метод потока Suspend() (переводится с английского как "приостановить")):

//---------------------------------------------------------------------------

//вывод состояния передачи данных на экран
void __fastcall WriteThread::Printing()
{
 if(!fl) //проверяем состояние флажка
 {
 Form1->StatusBar1->Panels->Items[0]->Text = "Ошибка передачи";
 return;
 }
 Form1->StatusBar1->Panels->Items[0]->Text = "Передача прошла успешно";

 writer->Suspend(); //приостановить поток записи в порт, пока он не потребуется снова
 
}

//---------------------------------------------------------------------------

Метод Suspend() останавливает выполнение потока на некоторое время, пока не понадобится возобновить его работу с помощью метода Resume().

Когда после остановки мы снова возобновим поток с помощью метода Resume(), он продолжит выполнение с точки останова, то есть перейдёт на следующий оператор после writer->Suspend(), то есть вернёт управление функции Execute() и перейдёт на начало цикла while(!Terminated), после чего выполнит запись данных в порт, вывод результата в строке состояния и снова остановится.

Была обнаружена интересная особенность - если метод Suspend() для потока записи вызывался в функции Printing, то информация о результате операции, а также введённая в функцию в самом конце тестовая строка выводились независимо от того, в каком месте находился вызов метода. То есть работало даже так:

//вывод состояния передачи данных на экран
void __fastcall WriteThread::Printing()
{
 writer->Suspend(); //приостановить поток записи в порт, пока он не потребуется снова
 if(!fl) //проверяем состояние флажка
 {
 Form1->StatusBar1->Panels->Items[0]->Text = "Ошибка передачи";
 return;
 }
 Form1->StatusBar1->Panels->Items[0]->Text = "Передача прошла успешно";
 Form1->Memo1->Lines->Add("Тест!");
}

и так тоже:

//вывод состояния передачи данных на экран
void __fastcall WriteThread::Printing()
{
 if(!fl) //проверяем состояние флажка
 {
 Form1->StatusBar1->Panels->Items[0]->Text = "Ошибка передачи";
 return;
 }
 Form1->StatusBar1->Panels->Items[0]->Text = "Передача прошла успешно";
 writer->Suspend(); //приостановить поток записи в порт, пока он не потребуется снова
 Form1->Memo1->Lines->Add("Тест!");
}

Это связано с тем, что метод Synchronize выполняет переданный ему метод в контексте главного потока приложения, а поток, вызвавший Synchronize, на это время приостанавливается. Поэтому и не имеет значения, в каком месте функции выполнять останов потока, если функция вызывается с помощью метода Synchronize.

Но вот если поставить вызов метода Suspend в главной функции потока следующим образом:

while(!Terminated) //пока поток не будет завершён, выполнять цикл
 {
 ............
 writer->Suspend(); //приостановить поток записи в порт, пока он не потребуется снова
 Synchronize(Printing); //вывести состояние операции в строке состояния
 }

то в данном случае функция Printing выполнялась только после повторного запуска потока, что доказывает, что после вызова метода Resume() поток продолжает своё выполнение с точки останова.

Аналогичный эффект можно достичь следующим образом: в функции Printing вызвать метод Suspend()

//вывод состояния передачи данных на экран
void __fastcall WriteThread::Printing()
{
 if(!fl) //проверяем состояние флажка
 {
 Form1->StatusBar1->Panels->Items[0]->Text = "Ошибка передачи";
 return;
 }
 Form1->StatusBar1->Panels->Items[0]->Text = "Передача прошла успешно";

 writer->Suspend(); //приостановить поток записи в порт, пока он не потребуется снова
}

а в главной функции потока добавить тестовую строку после вызова функции Printing:

//главная функция потока, выполняет передачу байтов из буфера в COM-порт
void __fastcall WriteThread::Execute()
{
 while(!Terminated) //пока поток не будет завершён, выполнять цикл
 {
 ..........
 Synchronize(Printing); //вывести состояние операции в строке состояния
 Form1->Memo1->Lines->Add("Тест!");
 }
 CloseHandle(overlappedwr.hEvent); //перед выходом из потока закрыть объект-событие
}

В этом случае информация о состоянии операции выведется в панель состояния, а вот тестовая строка появится только при следующем запуске потока.

Поэтому вызов метода Suspend лучше поместить так, чтобы поток выполнил всю необходимую работу (передача данных и вывод результата операции в строке состояния), и только потом был остановлен, чтобы при последующем запуске сразу перейти на начало цикла и выполнить такую же работу.

И всё же лучше всего вызов метода Suspend() поместить в конце цикла в главной функции, после вызова функции Printing:

//главная функция потока, выполняет передачу байтов из буфера в COM-порт
void __fastcall WriteThread::Execute()
{
 while(!Terminated) //пока поток не будет завершён, выполнять цикл
 {
 ..........
 Synchronize(Printing); //вывести состояние операции в строке состояния
 writer->Suspend(); //приостановить поток записи в порт, пока он не потребуется снова
 }
 CloseHandle(overlappedwr.hEvent); //перед выходом из потока закрыть объект-событие
}

Вы можете сами сделать так, как вам удобнее. Этот случай лучше, потому что здесь останов потока происходит именно в конце цикла, а не в функции. Иначе, если вам понадобится добавить ещё какую-нибудь функцию после функции Printing, то вызов этого метода вам придётся переносить в новую функцию. Это не очень удобно и вносит некоторую путаницу.

3) Интересный момент - это уничтожение потока методом Terminate(). Вот как выглядит код завершения потока в случае использования данных методов:

//если поток записи существует, подать ему команду на завершение и запустить его,
//чтобы он выполнил завершение
 if(writer) //проверка if(writer) обязательна, иначе возникают ошибки
 {
 writer->Terminate();
 writer->Resume();
 }

Так как поток записи активируется только когда необходимо передать данные, велика вероятность, что когда потребуется его завершить, поток будет находиться в остановленном состоянии и не сможет выполнить завершение. Тогда после вызова метода Terminate(), который установит в true свойство потока Terminated, необходимо вызвать метод Resume(), чтобы активировать поток. В этом случае поток сможет выполнить проверку на значение Terminated=true (в цикле while(!Terminated)) и завершиться.

В качестве особенностей описанных здесь методов можно отметить, что они имеют свойство накладываться. То есть если вы вызвали метод Suspend() несколько раз, то чтобы возобновить работу потока, вам нужно будет столько же раз вызвать метод Resume().

Категория: Мои статьи | Добавил: MAS (26.11.2012)
Просмотров: 1120 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Категории раздела
Мои статьи [51]
Справочные данные [165]
Справочные данные
Наш опрос
Оцените мой сайт
Всего ответов: 30
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
Форма входа
Баннер
$$
Друзья сайта
  • Спортсменам
  • Огородникам СЮД(А
  • Строим вместе
  • Мир развлечений
  • Кто хочет похудеть
  • Здоровье у нас одно
  • Юмор
  • Кулинарные рецепты
  • Картинки_Заставки_Демотивоторы
  • Семья
  • Кто на мальчишник
  • Металлообработка
  • Кто на девишник
  • Блоки питания
  • Смерть соседям
  • Радиомедведь
  • Кибермедведь
  • Радиосайт
  • Деревообработка
  • Поиск