На этом шаге мы рассмотрим алгоритм создания рабочего потока.
Для рабочего потока необходимо написать код управляющей функции, предназначенной для реализации основной
задачи потока. Ее адрес передается в качестве параметра функции AfxBeginThread(). Управляющая функция
имеет следующий синтаксис:
UINT MyControllingFunction(LPVOID pParam);
Единственный параметр функции - 32-разрядное число, которое можно использовать по своему усмотрению: просто
его проигнорировать либо передать функции через этот параметр число или адрес структуры, содержащей массив
данных. В последнем случае разрешается также передать данные обратно из порожденного потока в вызвавший его поток.
Объявление соответствующей версии функции AfxBeginThread() имеет следующий вид:
CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);
В первых параметрах содержатся адрес управляющей функции передаваемый ей параметр. В остальных параметрах
(имеющих значения по умолчанию) задаются приоритет потока, размер стека, а также поведение потока сразу после его
создания (состояние ожидания или немедленный запуск). Последний параметр определяет атрибуты защиты потока;
установленное по умолчанию значение NULL указывает, что поток наследует атрибуты родительского потока.
Функция AfxBeginThread() создает новый объект CWinThread, вызывает функцию CreateThread()
для запуска потока и возвращает указатель на него. В ходе этой процедуры проводятся проверки, подтверждающие правильность
освобождения всех объектов при возникновении сбоя на каком-либо этапе создания. Завершается поток самостоятельно,
если функция потока содержит оператор return или при вызове глобальной функции AfxEndThread().
Возвращаемое значение в общем случае указывает на причину завершения. По существующей традиции при успешном
завершении функция возвращает ноль, а ненулевое значение содержит код ошибки.
Проиллюстрируем создание рабочего потока для приложения МуАрр. Создадим простую функцию таймера,
предназначенную для вывода информационного окна по достижению за данного времени. Пользователь настроит
таймер в специальном диалоговом окне, после чего каждую секунду рабочий поток будет проверять системные часы.
По достижении заданного интервала поток выведет информационное окно и завершит работу.
Создадим класс CTimer, инкапсулирующий таймер. В этом классе для хранения времени будет создана
защищенная MFC-переменная CTime и открытый указатель CWinThread, позволяющий oпределить,
ссылается ли объект CTimer на активный поток.
- Создание класса CTimer.
- Откройте проект МуАрр. На вкладке FileView дважды щелкните значок файла MainFrm.cpp.
- В начало файла после операторов #include добавьте следующий код:
class CTimer { protected: CTime m_time; public: CWinThread *m_thread; CTimer() {Reset();} CTime GetTime() {return m_time; } void SetTime(CTime time) {m_time = time;} void Reset() { m_time = CTime::GetCurrentTime(); m_thread = NULL; } };
Рис.1. Описание класса CTimer
Функция GetCurrentTime(), будучи статическим членом класса CTime, возвращает текущее системное
время в формате CTime. - Непосредственно под объявлением класса добавьте объявление глобального объекта CTimer:
CTimer g_timer;
Далее создадим функцию рабочего потока, которая с интервалом в одну секунду будет сравнивать системное время и
время таймера. При их совпадении функция выведет информационное окно и переустановит таймер.
- Добавление функции DoTimer().
- В файле MainFrm.cpp непосредственно под объявлением класса поместите следующее описание функции:
UINT DoTimer(LPVOID pparam) { CTime currenttime = CTime::GetCurrentTime(); while(currenttime < g_timer.GetTime()) { Sleep(1000); currenttime = CTime::GetCurrentTime(); } AfxMessageBox("Time's up!"); g_timer.Reset(); return 0; }
Функция Sleep() переводит поток, в котором она вызвана, в состояние ожидания на заданное число миллисекунд.
Рис.2. Добавление функции DoTimer() - Сейчас подготовим диалоговое окно для установки времени таймера. Посредством редактора диалогов создайте
диалоговое окно.
Рис.3. Создание диалогового окна
Установите для диалогового окна идентификатор IDD_TIMER. Элемент управления, похожий на поле со
списком, доступен на панели инструментов элементов управления под названием Date Time Picker. Задайте для
него идентификатор IDC_DTPSETTIME и свойство, в соответствии с которым он будет показывать только время. - Настройка элемента управления IDC_DTPSETTIME для показа только времени.
- В редакторе диалогов выберите элемент управления IDC_DTPSETTIME. Нажмите ENTER, чтобы
изменить его свойства. - Щелкните вкладку Styles. В списке Format выберите Time.
- Щелкните за пределами окна свойств, чтобы закрыть его.
- Создание класса CTimerDialog.
- В редакторе диалогов нажмите CTRL+W, чтобы открыть СlassWizard. Создайте класс CTimerDialog.
- Щёлкните вкладку Member Variables. Создайте переменную-член категории Value для
идентификатора IDC_DTPSETTIME. Это должна быть переменная типа CTime с именем m_settime.
Рис.4. Добавление переменной m_settime - Щелкните ОК, чтобы закрыть ClassWizard.
- Добавление в меню View пункта Timer
и создание функции-обработчика OnViewTimer(). - С помощью редактора меню создайте в меню View команду Timer.
- Подтвердите идентификатор ID_VIEW_TIMER, предлагаемый по умолчанию.
- В ClassWizard на вкладке Message Maps создайте обработчик команды для объекта с
идентификатором ID_VIEW_TIMER в классе CMainFrame. Назовите функцию OnViewTimer().
Рис.5. Добавление функции OnViewTimer() - Добавив обработчик, щелкните кнопку Edit Code чтобы открыть для редактирования функцию CMyAppApp::OnViewTimer().
Поместите в тело функции следующий код:CTimerDialog aTDlg; aTDlg.m_settime = g_timer.GetTime(); if (aTDlg. DoModal() == IDOK) { g_timer.SetTime(aTDlg.m_settime); // Only one timer running per instance if(!g_timer.m_thread) g_timer.m_thread = AfxBeginThread(DoTimer, 0); }
Рис.6. Текст функции OnViewTimer() - В начале файла MainFrm.cpp рядом с другими операторами #include введите следующую строку:
#include "TimerDialog.h"
- Откомпилируйте и соберите приложение МуАрр. Проверьте работу таймера, выбрав в меню View команду Timer.
Появится одноименное диалоговое окно, в котором элемент управления Date Time Picker показывает текущее время. Установите
новое время, превышающее текущее, напримерЮ на одну минуту и нажмите кнопку OK.
Теперь создадим класс диалога для диалогового окна Timer.
Теперь осталось лишь добавить команду и функцию-обработчик для установки таймера и запуска потока таймера.
Во время ожидания таймера поэкспериментируйте с другими функциями интерфейса МуАрр. Вы не заметите
снижения их производительности за счет работы независимого рабочего потока, каждую секунду проверяющего системное время.
Когда системные часы покажут время, установленное в диалоговом окне Timer, появится информационное окно и рабочий поток
завершится.
Текст измененного приложения можно взять здесь (64,9 Кб).
На следующем шаге мы рассмотрим создание потока пользовательского интерфейса.