    ВОЛОКНА 

    приложение A 

    В Microsoft Windows NT 3.51 (Service Pack 3) появился новый механизм — 
волокна (fibers). Функции для работы с волокнами добавили в Win32 для 
упрощения переноса серверных приложений из UNIX в Windows NT. Такие серверные 
приложения следует считать в терминологии Win32 однопоточными, однако их 
структура позволяет обслуживать множество клиентов. Иначе говоря, разработчики 
этих приложений создали свою библиотеку для организации многопоточности и 
использовали ее для эмуляции истинных потоков. Она создает набор стеков, 
сохраняет определенные регистры процессора и переключает контексты при 
обслуживании запросов. 
    Разумеется, чтобы добиться большей производительности от этих 
UNIX-приложений, их следует перепроектировать, заменив библиотеку, эмулирующую 
потоки, на настоящие потоки, используемые в Windows. Но переработка под Win32 
может занять несколько месяцев, и поэтому компаниям нужно было выпустить 
промежуточные версии программ — это позволило бы, не теряя рынка, быстро 
перенести их в среду Windows. 
    Но при переносе UNIX-программ в Win32 могут возникнуть проблемы. В 
частности, механизм управления стеком потока в Windows куда сложнее простого 
выделения памяти. В Windows стеки начинают работать, располагая сравнительно 
малым объемом физической памяти, и растут по мере необходимости (об этом я 
рассказывал в разделе «Стек потока» главы 7). Перенос усложняется и наличием 
механизма структурной обработки исключений (глава 16). 
    Чтобы помочь быстрее (и с меньшим числом ошибок) перенести код в Win32, 
Microsoft добавила в операционную систему механизм волокон. Это безусловно 
полезное расширение API, но лучше использовать настоящие потоки. А теперь, 
когда я предупредил Вас, перейдем к концепции волокон и к функциям, 
предназначенным для операций с ними. Кроме того, я расскажу, как эффективнее 
работать с такими функциями. 
    Во-первых, потоки в Windows реализуются на уровне ядра операционной 
системы, которое отлично знает об их существовании и «коммутирует» их в 
соответствии с созданным Microsoft алгоритмом. В то же время волокна 
реализованы на уровне кода, работающего в режиме User, ядро ничего не знает о 
них, и процессорное время распределяется между волокнами по алгоритму, 
определяемому Вами. А раз так, то о вытеснении волокон говорить не приходится 
— по крайней мере когда дело касается ядра. 
    Второе, о чем следует помнить, — в потоке может быть одно или несколько 
волокон. Для ядра поток — все то, что можно вытеснить и что выполняет код. Но 
единовременно поток будет выполнять код лишь одного волокна — какого, решать 
Вам (соответствующие концепции я поясню позже). 

    647 

    648 _____________________________„Windows для профессионалов 

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

    LPVOID ConvertThreadToFiber(LPVOID IpParameter); 

    Она создает в памяти контекст волокна (размером около 200 байтов). В него 
входят следующие элементы: 
    • определенное программистом 32-битное значение; оно получает значение 
параметра IpParameter, передаваемого в ConvertThreadToFiber, 
    • вершина цепочки структурной обработки исключений; 
    • начальный и конечный адреса стека волокна; при преобразовании потока в 
волокно он служит и стеком потока; 
    • регистры процессора, включая указатели стека и команд. 
    Создав и инициализировав контекст волокна, функция сопоставляет его адрес 
с потоком, преобразованным в волокно, и теперь оно выполняется в этом потоке. 
ConvertThreadToFiber возвращает адрес, по которому расположен контекст 
волокна. Этот адрес еще понадобится нам, но ни считывать, ни записывать по 
нему напрямую ни в коем случае нельзя — с содержимым этой структуры работают 
только функции, управляющие волокнами. При вызове ExitThread завершаются и 
волокно, и поток. 
    Нет смысла преобразовывать поток в волокно, если Вы не собираетесь 
создавать дополнительные волокна в том же потоке. Чтобы создать другое 
волокно, поток (выполняющий в данный момент волокно), вызывает функцию CreateFiber. 

    LPVOID CreateFiber(DWORD dwStackSize, 
    LPFIBER_START_ROUTINE IpStartAddress, LPVOID IpParameter); 

    Сначала она пытается создать новый стек, размер которого задан в параметре 
dwStackSize. Обычно передают 0, и тогда максимальный размер стека 
ограничивается 1 Мб, а изначально ему передается 2 страницы памяти. Если Вы 
укажете ненулевое значение, то для стека будет зарезервирован и передан именно 
такой объем памяти. 
    Создав стек, CreateFiber формирует новую структуру контекста волокна и 
инициализирует ее. При этом первый ее элемент получает значение, переданное 
функции как параметр IpParameter, сохраняются начальный и конечный адреса 
стека, а также адрес функции волокна (переданный как аргумент IpStartAddress). 
    У функции волокна, реализуемой Вами, должен быть такой прототип: 

    VOID WINAPI FiberFunc(PVOID IpParameter); 

    Она выполняется, когда волокно впервые получает управление. В качестве 
параметра ей передается значение, изначально переданное как аргумент 
IpParameter функции CreateFiber. Внутри функции волокна можно делать что 
угодно. Обратите внимание на тип возвращаемого значения — VOID. Причина не в 
том, что это значение несущественно, а в том, что функция никогда не 
возвращает управления! А если возврат из нее и произошел бы, поток и все 
созданные внутри него волокна были бы уничтожены. 
    Как и ConvertThreadToFiber, CreateFiber возвращает адрес контекста 
волокна, но с тем отличием, что новое волокно начинает работать не сразу, 
поскольку продолжается выполнение текущего. Единовременно поток может 
выполнять лишь одно волокно. И, чтобы новое волокно приступило к работе, надо 
вызвать SwitchToFiber. VOID SwitchToFiber(LPVOID IpFlber); 
    Эта функция принимает единственный параметр (IpFiber) — адрес контекста 
волокна, полученный в предшествующем вызове ConvertThreadToFiber или 
CreateFiber. По этому адресу она определяет, какому волокну предоставить 
процессорное время. SwitchToFiber осуществляет такие операции: 

    _________________________________ Приложение А. Волокна 649 

    1. Сохраняет в контексте текущего волокна ряд текущих регистров 
процессора, включая указатели команд и стека. 
    2. Загружает в регистры процессора значения, сохраненные в контексте 
волокна, подлежащего выполнению. В их число входит указатель стека, и поэтому 
при переключении на другое волокно используется именно его стек. 
    3. Связывает контекст волокна с потоком, который теперь выполняет 
указанное волокно. 
    4. Восстанавливает указатель команд. Поток (волокно) продолжает выполнение 
с того места, на каком оно было прервано в последний раз. 
    Применение SwitchToFiber — единственный способ выделить волокну 
процессорное время. Поскольку Ваш код должен явно вызывать эту функцию в 
нужные моменты, Вы полностью управляете распределением процессорного времени 
для волокон. Помните: такой вид диспетчеризации не имеет ничего общего с 
диспетчеризацией потоков. Поток, в рамках которого выполняются волокна, всегда 
может быть вытеснен операционной системой. Когда поток получает процессорное 
время, выполняется только выбранное волокно, и никакое другое не получит 
управления, если Вы не переключитесь на него явным вызовом SwitchToFiber. 
    Чтобы уничтожить волокно, вызовите DeleteFiber. 

    VOID DeleteFiber(LPVOID IpFiber); 

    Эта функция удаляет волокно, чей адрес контекста определяется параметром 
IpFiber, освобождает память, занятую стеком волокна, и уничтожает его 
контекст. Но, если Вы передаете адрес волокна, связанного в данный момент с 
потоком, DeleteFiber сама вызывает ExitThread — в результате поток и все 
созданные в нем волокна «погибают». 
    DeleteFiber обычно вызывается волокном, чтобы удалить другое волокно. Стек 
удаляемого волокна уничтожается, а его контекст освобождается. И здесь 
обратите внимание на разницу между волокнами и потоками: потоки, как правило, 
уничтожают себя сами, обращаясь к ExitThread. Использование с этой целью 
TeminateThread считается плохим тоном — ведь тогда система не уничтожает стек 
потока. Так вот, способность волокна корректно уничтожать другие волокна нам 
еще пригодится — как именно, я расскажу, когда мы дойдем до программы-примера. 
    Для удобства предусмотрены еще две функции, управляющие волокнами. В 
каждый момент потоком выполняется лишь одно волокно, и операционная система 
всегда знает, какое волокно связано сейчас с потоком. Чтобы получить адрес 
контекста текущего волокна, вызовите GetCurrentFiber. 

    PVOID GetCurrentFiber(VOID); 

    Другая полезная функция — GetFiberData: 

    PVOID GetFiberData(VOID); 

    Как я уже говорил, контекст каждого волокна содержит определяемое 
программистом 32-битное значение. Оно инициализируется значением параметра 
IpParameter, передаваемого функциям ConvertThreadToFiber и CreateFiber, и 
служит аргументом функции волокна. GetFiberData просто «заглядывает» в 
контекст текущего волокна и возвращает хранящееся там 32-битное значение. 
    Обе функции — GetCurrentFiber и GetFiberData — работают очень быстро и 
обычно реализуются компилятором как встраиваемые (т. е. вместо вызовов он 
подставляет код этих функций). 

    650 _____________________________Windows для профессионалов 


    Программа-пример Counter 

    Программа Counter (COUNTER. ЕХЕ) (см. листинг на рис. А-1) демонстрирует 
применение волокон для реализации фоновой обработки. Запустив ее, Вы увидите 
диалоговое окно, показанное ниже. (Настоятельно советую запустить программу 
Counter; так будет легче понять, что происходит в ней и как она себя ведет.) 

    Count to:   3              Answer:        Ой-ч 
    Currently-running fiber:   User-interface 

    Считайте эту программу сверхминиатюрной электронной таблицей, состоящей 
всего из двух ячеек. В первую можно записывать — она реализована как поле 
ввода, расположенное за текстом «Count to» (считать до). Вторая — доступна 
только для чтения и реализована как статический элемент управления, 
размещенный за текстом «Answer» (результат). Изменив число в поле ввода, Вы 
заставите программу пересчитать значение в ячейке Answer. В этом простом 
примере пересчет заключается в том, что счетчик, начальное значение которого 
равно 0, постепенно увеличивается до максимума, заданного в ячейке Count to. 
Для наглядности статический элемент управления, расположенный в нижней части 
диалогового окна, показывает, какое из волокон — интерфейсное (User-interface) 
или расчетное (Recalculation) — выполняется в данный момент. 
    Чтобы протестировать программу, запишите в поле ввода число 5 — появится 
строка Recalculation, а значение в поле Answer постепенно возрастет с 0 до 5. 
Когда пересчет закончится, текущим волокном вновь станет интерфейсное, а поток 
заснет. Теперь введите число 50 и вновь понаблюдайте за пересчетом — на этот 
раз перемещая окно по экрану. При этом Вы заметите, что расчетное волокно 
вытесняется, а интерфейсное вновь получает процессорное время, благодаря чему 
интерфейс программы продолжает реагировать на действия пользователя. Оставьте 
окно в покое, и Вы увидите, что расчетное волокно снова получило управление и 
возобновило работу с того значения, на котором было прервано. 
    Остается проверить лишь одно. Давайте изменим число в поле ввода в момент 
пересчета. Интерфейс отреагировал на Ваши действия, но после ввода данных 
пересчет начинается заново. Таким образом, программа ведет себя, как настоящая 
электронная таблица. 
    Обратите внимание и на то, что в программе не задействованы ни критические 
секции, ни другие объекты, синхронизирующие потоки, — все сделано на основе 
двух волокон в одном потоке. Ну а теперь обсудим внутреннюю реализацию 
программы Counter. 
    Когда первичный поток процесса приступает к выполнению WinMain (см. ее в 
конце листинга на рис. А-1), вызывается функция ConvertThreadToFiber, 
преобразующая поток в волокно (впоследствии это позволит создать другое 
волокно). Затем мы создаем немодальное диалоговое окно, выступающее в роли 
основного окна программы. Далее инициализируем переменную — индикатор 
состояния фоновой обработки (background processing state, BPS). Она 
реализована как элемент bps в глобальной переменной g_FiberInfo. Возможны 3 состояния: 

    Состояние         Описание 

    BPS_DONE         Пересчет завершен, пользователь больше ничего не изменял, 
новый пересчет не нужен. 
    BPS_STARTOVER    Пользователь внес изменения, требующие пересчета с самого начала. 
    BPS_CONTINUE     Пересчет еще продолжается, пользователь ничего не 
изменял, и пересчета заново не требуется. 

    ___________________________ Приложение А. Волокна 651 

    Индикатор bps проверяется внутри цикла обработки сообщений потока, который 
здесь сложнее обычного. Вот что делает этот цикл. Сначала, если поступило 
оконное сообщение (активен пользовательский интерфейс), обрабатываем именно 
его. Своевременная обработка действий пользователя всегда приоритетнее пересчета. 
    Если пользовательский интерфейс простаивает, проверяем, не нужен ли 
пересчет (т. е. не присвоено ли переменной bps значения BPS_STARTOVER или 
BPS_CONTINUE). Если вычислений делать не нужно (BPS_DONE), приостанавливаем 
поток, вызывая WaitMessage, — только событие, связанное с пользовательским 
интерфейсом, может потребовать пересчета. 
    Если интерфейсному волокну делать нечего, а пользователь только что 
изменил значение в поле ввода, начинаем вычисления заново (BPS_STARTOVER). 
Главное, о чем здесь надо помнить, — волокно, отвечающее за пересчет, может 
уже работать. Тогда это волокно надо удалить и создать новое, которое начнет 
все с начала. Чтобы уничтожить выполняющее пересчет волокно, интерфейсное 
вызывает DeleteFiber. Именно этим и удобны волокна. Удаление волокна, занятого 
пересчетом, — операция вполне допустимая, стек волокна и его контекст 
корректно уничтожаются. Если бы мы использовали потоки, а не волокна, 
интерфейсный поток не смог бы корректно уничтожить поток, занятый пересчетом, 
— нам пришлось бы задействовать какой-нибудь механизм межпоточного 
взаимодействия и ждать, пока поток пересчета не завершится сам. Зная, что 
волокна, отвечающего за пересчет, больше нет, мы вправе создать новое волокно 
для тех же целей, присвоив переменной bps значение BPS_CONTINUE. 
    Когда пользовательский интерфейс простаивает, а волокно пересчета чем-то 
занято, мы выделяем ему время, вызывая SwitchToFiber. Последняя не вернет 
управления, пока волокно пересчета тоже не обратится к SwitchToFiber, передав 
ей контекст интерфейсного волокна как аргумент. 
    Counter_FiberFunc является функцией волокна и содержит код, выполняемый 
волокном пересчета. Ей передается адрес глобальной структуры g_Fiber!nfo, и 
поэтому она знает описатель диалогового окна, адрес контекста интерфейсного 
волокна и текущее состояние индикатора фоновой обработки. Конечно, раз это 
глобальная переменная, то передавать ее адрес как параметр не обязательно, но 
я решил показать, как в функцию волокна передаются параметры. Кроме того, 
передача адресов позволяет добиться того, чтобы код меньше зависел от 
конкретных переменных, — именно к этому и следует стремиться. 
    Первое, что делает функция волокна, — обновляет диалоговое окно, сообщая, 
что сейчас выполняется волокно пересчета. Далее функция получает значение, 
набранное в поле ввода, и запускает цикл, считающий от 0 до указанного 
значения. Перед каждым приращением счетчика вызывается GetQueueStatus — эта 
функция проверяет, не появились ли в очереди потока новые сообщения (все 
волокна, работающие в рамках одного потока, делят его очередь сообщений). 
Сообщение появилось — значит, интерфейсному волокну есть чем заняться, и мы, 
считая его приоритетным по отношению к расчетному, сразу же вызываем 
SwitchToFiber, давая ему возможность обработать поступившее сообщение. Когда 
сообщение (или сообщения) будет обработано, интерфейсное волокно передаст 
управление волокну, отвечающему за пересчет, и фоновая обработка возобновится. 
    Если же сообщений нет, расчетное волокно обновляет поле Answer диалогового 
окна и засыпает на 200 мс. В коде настоящей программы вызов Sleep надо, 
естественно, убрать — я поставил его, только чтобы «потянуть» время. 
    Когда волокно, отвечающее за пересчет, завершает свою работу, статус 
фоновой обработки устанавливается как BPS_DONE, и управление передается (через 
SwitchToFiber) интерфейсному волокну. В этот момент ему делать нечего, и оно 
вызывает WaitMessage, которая приостанавливает поток, чтобы не тратить 
процессорное время понапрасну. 

    652 ______________________________Windows для профессионалов 


    COUNTER.C 
    /...,,,*,***,**,„*,....****„**.;,*...**.„*...*,***,*.**„*...*«.***..*..,*. 
    Модуль: Counter.С Автор: Copyright © 1996-1997, Джеффри Рихтер (Jeffrey Richter) 
    #include ",.\CmnHdr.h" 
    #include <Windows.h> 
    ^include <WindowsX,h> 
    ffinclude <tchar.n> 
    ftinclude "Resource.h" 
    /////////////////////////////////////////////////////////////////////////////// 
    // возможные состояния фоновой обработки 
    typedef enum { 
    BPS_STARTOVER,   // начинаем фоновую обработку заново 
    BPSJ30NTINUE,    // продолжаем фоновую обработку 
    BPS_DONE        // фоновая обработка не нужна 
    } BKGNDPROCSTATE; 
    typedef struct { 
    // контекст интерфейсного волокна 
    PVOID pFiberUI; 
    // описатель основного окна 
    HWND hwnd; 
    // состояние фоновой обработки 
    BKGNDPROCSTATE bps: 
    } FIBERINFO. *PFIBERINFO; 
    // Глобальная переменная, содержащая информацию о состоянии приложения. 
    // Из интерфейсного волокна она доступна напрямую, а из волокна, отвечающего 
    // за фоновую обработку, - косвенно. 
    FIBERINFO g_Fiber!nfo; 
    lllll/ll///i/////ll//l/lillllH//ll/lll/l!ll/l/l//lll/l/IIIIIIIIIIH/llllll/lll 
    void WINAPI Counter_FiberFLinc (LPVOIO pvParam) { 
    PFIBERINFO pFiberInfo = (PFIBERINFO) pvParain; 
    BOOL rTranslated; 
    int x, nCount; 
    
    // показываем в окне, какое волокно выполняется сейчас 
    SetDlgItemText(pFiberInfo->hwnd, IDC_FIBER, __TEXT("Recalculation")); 
    
    // получаем текущее значение из поля ввода 
    nCount = GetDlgItemInt( 
    pFiberInfo->hwnd, IDC_GOUNT, SfTranslated, FALSE); 

    см. след. стр. 

    Рис. А-1. Листинг программы Counter 

    ______________________________ Приложение А. Волокна 653 


    РИС. А-1   (продолжение) 
    
    I/ считаем от 0 до nCount, обновляя статический элемент управления 
    for (х = 0; х <= nCount: х++) { 
    
    // события пользовательского интерфейса приоритетнее расчетов 
    // (если такие события есть, обрабатываем их) 
    if (HIWORD(GetQueueStatus(QS_ALLEVENTS)) != 0) { 
    
    // Интерфейсное волокно чем-то занято. Временно приостанавливаем 
    // пересчет и обрабатываем события пользовательского интерфейса. 
    SwitchToFiber(pFiberInfo->pFiberUI); 
    
    // событий больше нет, возобновляем пересчет 
    SetDlgItemText(pFiberInfo->hwnd, IDC_FIBER, __TEXT("Recalculation")): 
    
    // обновляем статический элемент управления, 
    // показывая последнее значение счетчика 
    SetDlgItefflInt(pFiberInfo->hwnd, IDC_ANSWER, x, FALSE); 
    // засыпаем, чтобы "потянуть" время; 
    // в рабочем коде вызов Sleep надо удалить 
    81еер(200); 
    \ 
    
    // сообщаем, что пересчет закончен 
    pFiberInfo->bps = BPS_DONE; 
    
    // Выделяем процессорное время интерфейсному волокну. Если 
    // событий, подлежащих обработке, нет, приостанавливаем его. 
    // ВНИМАНИЕ: если разрешить возврат из функции волокна, 
    // поток и интерфейсное волокно завершатся, а мы этого не хотим! 
    SwitchToFiber(pFiberInfo->pFiberUI); 
    i 
    J 11/11111111111111111111111111/1111111111111111111111111111111111111111111111111 
    BOOL Counter_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM IParam) { 
    // связываем значок с диалоговым окном 
    chSETDLGICONS(hwnd, IDI_COUNTER, IDI_COUNTER); 
    ftifdef _DEBUG 
    
    // в отладочной версии сдвигаем окно в левый верхний 
    // угол экрана, освобождая пространство для отладчика 
    SetWindowPos(hwnd, NULL, О, О, О, О, SWP_NOZORDER ; SWP_NOSIZE); 
    ffendif 


    654 __ __________________________ Windows для профессионалов 


    РИС. А~1   (продолжение) 

    SetDlgItemInt(hwnd, IDC_COUNT, О, FALSE); 
    return(TRUE): 
    void Counter_OnComniand(HWND hwnd, int id. HWND hwndCtI, UINT codeNotify) { 
    switch (id) { 
    case IDCANCEL: 
    
    // если нажата клавиша Esc, уничтожаем немодальное 
    // диалоговое окно и завершаем программу 
    DestroyWindow(hwnd); 
    break: 
    case IDC_COUNT: 
    if (codeNotify == EN_CHANGE) { 
    // пользователь изменил значение счетчика, 
    // начинаем фоновую обработку заново 
    g_FiberInfo.bps = BPS_STARTOVER; 
    } 
    break; 
    
    BOOL WINAPI Counter_DigProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM 
IParam) { 
    switch (uMsg) { 
    HANDLE_MSG(hwnd, WM_INITDIALOG, Counter_OnInitDialog); 
    HANDLE_MSG(hwnd, WM_COMMAND, Counter_OnCommand); 
    } 
    return(FALSE); 
    i 
    /////////////////////////////////////////////////////////////////////////////// 
    int WINAPI _tWinMain (HINSTANCE hinstExe, 
    HINSTANCE hinstPrev, LPTSTR pszCmdLine, int nCmdShow) { 
    // контекст волокна, отвечающего за пересчет 
    PVOID pFiberCounter = NULL; 
    chWARNIFUNICODEUNDERWIN95(); 
    // преобразуем поток в волокно 
    g_FiberInfo,pFiberUI = ConvertThreadToFiber(NULL); 
    // создаем окно программы 

    см. след. стр. 

    ________________________________Приложение А. Волокна 655 


    РИС. А-1   (продолжение) 

    g_FiberInfo.hwnd = CreateDialog(hinstExe, 
    MAKEINTRESOURCE(IDD_COUNTER), NULL, Counter_DlgProc); 
    // обновляем окно, показывая, какое волокно 
    // выполняется в данный момент 
    SetDlgIteniText(g_FiberInfo.hwnd, IDC_FIBER, __TEXT("User-interface")); 
    
    // изначально фоновая обработка отсутствует 
    g_FiberInfo.bps = BPS_DONE: 
    
    // пока пользовательское окно существует,.. 
    while (IsWindow(g_FiberInfo.hwnd)) { 
    
    // интерфейсные сообщения приоритетнее 
    // фоновой обработки 
    MSG msg; 
    if (PeekMessage(&msg, NULL. О, 0, PM_REMOVE)) { 
    
    // если в очереди есть сообщение, обрабатываем его 
    if (!IsOialogMessage(g_FiberInfo.hwnd, &msg)) { 
    TranslateMessage(&msg); 
    DispatchMessage(&msg); 
    \ i 
    } else { 
    
    // если сообщений нет, проверяем состояние 
    // фоновой обработки 
    switch (g_FiberInfo.bps) { 
    case BPS_DONE: 
    // нет ни сообщений, ни необходимости в фоновой обработке; 
    // ждем события, связанного с пользовательским интерфейсом 
    WaitMessageO; 
    break; 
    case BPS_STARTOVER: 
    
    // пользователь изменил счетчик, 
    // начинаем фоновую обработку заново 
    if (pFiberCounLer != NULL) { 
    
    // если волокно, отвечающее за пересчет, уже существует, 
    // удаляем его, и фоновая обработка начнется заново 
    DeleteFiber(pFiberCounter); 
    pFiberCounter = NULL; 
    i i 

    см. след. cur, 

    656 _____________________________Windows для профессионалов 


    РИС. А-1   (продолжение) 

    II создаем новое волокно для пересчета, которое 
    // начнет все с начала 
    pFiberCounter = CreateFiber(0, Counter_FiberFunc, &g_FiberInfo); 
    // фоновая обработка началась, ее нужно продолжать 
    g_Fiber!nfo.bps = BPS_CONTINUE; 
    // переходим к BPS_CONTINUE... 
    case BPS_CONTINUE: 
    // переключаемся на волокно пересчета... 
    SwitchToFlber(pFiberCounter); 
    
    // фоновая обработка приостановлена из-за появления 
    // сообщения от пользовательского интерфейса или 
    // потому, что вычисления закончены 
    
    // обновляем окно, показывая, какое волокно 
    // выполняется в данный момент 
    SetDlgItemText(g_FiberInro.hwnd, 
    IDC_FIBER, __TEXT("User-interface")): 
    if (g_FiberInfo.bps == BPS_DONE) { 
    
    // если фоновая обработка закончена, удаляем 
    // его волокно, чтобы в следующий раз фоновая 
    // обработка началась заново 
    OeleteFiber(pFiberCounter); 
    pFiberCounter = NULL; 
    } 
    break; 
    } // конец оператора switch 
    } // сообщений от пользовательского интерфейса нет 
    } // while (окно существует) 
    return(O): // программа завершена 
    1 
    ////////////////////////////////// конец файла ///////////////////////////////// 