Программа-пример Counter
Эта программа, "12 Counter.exe" (см. листинг на рис 12-1), демонстрирует примснс нис волокон для реализации фоновой обработки. Запустив се, Вы увидите диалого вое окно, показанное ниже (Настоятельно советую запустить программу Counter, тогда Вам будет легче понять, что происходит в ней и как она себя ведет)
Считайте эту программу свсрхминиатюрной электронной таблицей, состоящей всего из двух ячеек. В первую из них можно записывать — она реализована как поле, расположенное за меткой Count To. Вторая ячейка доступна только для чтения и ре ализована как статический элемент управления, размещенный за меткой Answer Из менив число в поле, Вы заставите программу пересчитать значение в ячейке Answer. В этом простом примере пересчет заключается в том, что счетчик, начальное значе ние которого равно 0, постепенно увеличивается до максимума, заданного в ячейке Count То. Для наглядности статический элемент управления, расположенный в ниж ней части диалогового окна, показывает, какое из волокон — пользовательского ин терфейса или расчетное — выполняется в данный момент
Чтобы протестировать программу, введите в поле число 5 — строка Currently Running Fiber будет заменена строкой Recalculation, а значение в поле Answer посте пенно возрастет с 0 до 5. Когда пересчет закончится, текущим волокном вновь станет интерфейсное, а поток заснет Теперь введите число 50 и вновь понаблюдайте за пе ресчегом — на этот paз перемещяя окно по экрану. При этом Вы заметите, что рас четное волокно вытесняется, а интерфейсное вновь получает процессорное время, благодаря чему программа продолжает реагировать на действия пользователя. Оставь те окно в покое, и Вы увидите, что расчетное волокно снова получило управление и возобновило работу с того значения, на котором было прервано
Остается проверить лишь одно. Давайте изменим число в поле ввода в момент пересчета Заметьте: интерфейс отреагировал на Ваши действия, но после ввода дан ных пересчет начинается заново. Таким образом, программа ведет себя как настоя щая электронная таблица.
Обратите внимание и на то, что в программе не задействованы ни критические секции, ни другие объекты, синхронизирующие потоки, — все сделано на основе двух волокон в одном потоке
Теперь обсудим внутреннюю реализацию программы Counter Когда первичный поток процесса приступает к выполнению _tWinMain, вызывается функция Convert ThreadToFiber, преобразующая поток в волокно, которое впоследствии позволит нам создать другое волокно. Затем мы создаем немодальнос диалоговое окно, выступаю щее в роли главного окна программы. Далее инициализируем переменную — инди катор состояния фоновой обработки (background processing stale, BPS) Она реализо вана как элемент bps в глобальной переменной g_FiberInfo Ее возможные состояния описываются в следующей таблице.
Состояние | Описание |
BPS_DONE | Пересчет завершен, пользователь ничего не изменял, новый пересчет не нужен |
BPS_STARTOVER | Пользователь внес изменения, требуется пересчет с самою начала |
BPS_CONTINUE | Пересчет еще продолжается, пользователь ничего не изменил, пере счет заново не нужен |
Если интерфейсному волокну делать нечего, а пользователь только что изменил значение в поле ввода, начинаем вычисления заново (BPS_STARTOVER). Главное, о чем здесь надо помнить, — волокно, отвечающее за пересчет, может уже работать. Тогда это волокно следует удалить и создать новое, которое начнет все с начала.
Чтобы уничтожить выполняющее пересчет волокно, интерфейсное вызывает DeleteFiber. Именно этим и удобны волокна. Удаление волокна, занятого пересчетом, — операция вполне допустимая, стек волокна и его контекст корректно уничтожаются Если бы мы использовали потоки, а не волокна, интерфейсный поток не смог бы корректно уничтожить поток, занятый пересчетом, — нам пришлось бы задействовать какой нибудь механизм межпоточного взаимодействия и ждать, пока поток пересчета не завершится сам. Зная, что волокна, отвечающего за пересчет, больше нет, мы впра ве создать новое волокно для тех же целей, присвоив переменной bps значение BPS_CONTINUE.
Когда пользовательский интерфейс простаивает, а волокно пересчета чем-то за нято, мы выделяем ему процессорное время, вызывая SwitchToFiber, Последняя не вер пет управление, пока волокно пересчета тоже не обратится к SwitchToFiber, передав ей адрес контекста интерфейсного волокна.
FtberFunc является функцией волокна и содержит код, выполняемый волокном пересчета. Ей передается адрес глобальной структуры g_FiberInfo, и поэтому она зна ет описатель диалогового окна, адрес контекста интерфейсного волокна и текущее состояние индикатора фоновой обработки. Конечно, раз это глобальная переменная, то передавать ее адрес как параметр необязательно, но и решил показать, как в функ цию волокна передаются параметры. Кроме того, передача адресов позволяет добить ся того, чтобы код меньше зависел от конкретных переменных. - именно к этому и следует стремиться.
Первое, что делает функция волокна, — обновляет диалоговое окно, сообщая, что сейчас выполняется волокно пересчета. Далее функция получает значение, введенное в поле, и запускает цикл, считающий от 0 до указанного значения. Перед каждым приращением счетчика вызывается GetQueueStаtus — эта функция проверяет, не по
явились ли в очсрсди потока новые сообщения. (Все волокна, работающие в рамках одного потока, делят его очередь сообщений) Если сообщение появилось, значит, интерфейсному волокну есть чем заняться, и мы, считая его приоритетным по отно шению к расчетному, сразу же вызываем SwitchToFiber, давая ему возможность обра ботать поступившее сообщение Когда сообщение (или сообщения) будет обработа но, интерфейсное волокно передаст управление волокну, отвечающему за пересчет, и фоновая обработка возобновится.
Если сообщений нет, расчетное волокно обновляет поле Answer диалогового окна и засыпает на 200 мс. В коде настоящей программы вызов Sleep надо, естественно, убрать — я поставил его, только чтобы "потянуть" время.
Когда волокно, отвечающее за пересчет, завершает свою работу, статус фоновой обработки устанавливается как BPS_DONE, и управление передается (через Switch ToFiber) интерфейсному волокну. В этот момент ему делать нечего, и оно вызывает WaitMessage, которая приостанавливает поток, чтобы не тратить процессорное время понапрасну.
Counter