Выбрать главу

Для многопоточного программирования Windows можно выбирать между классами MFC и потоковыми функциями Win32. Microsoft рекомендует использовать в MFC-приложениях классы потоков. Для работы с потоками в MFC предусмотрены следующие классы:

• CWinThread

• CSyncObject

• CEvent

• CCriticalSection

• CMutex

• CSemaphore

• CSingleLock

• CMultiLock

Класс CWinThread представляет отдельный поток. Он присутствует во всех приложениях, потому что класс CWinApp (базовый для класса DirectDrawApp) является производным от CWinThread. Этот экземпляр класса CWinThread представляет основной поток приложения; чтобы добавить новые рабочие потоки, следует создать объекты CWinThread.

Класс CSyncObject является виртуальным. Непосредственное создание экземпляров этого класса не разрешается; он существует лишь для того, чтобы обеспечивать функциональные возможности производных классов. Класс CSyncObject является базовым для классов CEvent, CCriticalSection, CMutex и CSemaphore. Объекты синхронизации, представленные этими классами, рассматривались в предыдущем разделе.

Классы CSingleLock и CMultiLock применяются для блокировки потоков по состоянию одного или нескольких событий. Класс CSingleLock блокирует поток до установки конкретного события, а CMultiLock — до установки одного или всех событий из заданного набора.

Позднее в этой главе мы воспользуемся классами CWinThread, CEvent, CCriticalSSection и CMultiLock.

Решение проблемы курсора

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

Основной поток

Основной поток программы Cursor ведет себя почти так же, как и основные потоки всех остальных программ, рассмотренных нами. Он инициализирует DirectDraw, создает поверхности приложения, строит очередной кадр во вторичном буфере и переключает страницы для его отображения. Чтобы обеспечить работу потока ввода, нам придется возложить на основной поток следующие дополнительные задачи:

• создание и запуск потока ввода;

• обновление курсора перед каждым переключением страниц;

• синхронизацию с потоком ввода;

• завершение потока ввода.

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

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

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

Наконец, основной поток должен предупредить поток ввода о завершении приложения. Помните — поток ввода работает независимо от основного потока. Без извещения со стороны основного потока он не будет знать о том, что приложение собирается завершиться. Кроме того, основной поток не может просто остановить работу потока ввода; поток ввода должен завершиться сам при получении сигнала завершения от основного потока.

Поток ввода

Поток ввода обладает более узкой специализацией по сравнению с основным потоком. Он должен делать следующее:

• обнаруживать ввод от мыши;

• обновлять курсор;

• синхронизироваться с основным потоком;

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