Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

.pdf
Скачиваний:
6250
Добавлен:
13.08.2013
Размер:
31.38 Mб
Скачать

394 Часть II. Приступаем к работе

nWritesInProgress++;

break;

case CK_WRITE: // Write completed, read from source nWritesInProgress--;

if (liNextReadOffset.QuadPart < liFileSizeDst.QuadPart) {

// Not EOF, read the next block of data from the source file. bResult = pior->Read(hFileSrc, &liNextReadOffset); nReadsInProgress++;

liNextReadOffset.QuadPart += BUFFSIZE; // Advance source offset

}

break;

}

}

bOk = TRUE;

}

leave:;

}

catch (...) {

}

if (bOk) {

//The destination file size is a multiple of the page size. Open the

//file WITH buffering to shrink its size to the source file's size. CEnsureCloseFile hFileDst = CreateFile(pszFileDst, GENERIC_WRITE,

0, NULL, OPEN_EXISTING, 0, NULL); if (hFileDst.IsValid()) {

SetFilePointerEx(hFileDst, liFileSizeSrc, NULL, FILE_BEGIN); SetEndOfFile(hFileDst);

}

}

return(bOk);

}

///////////////////////////////////////////////////////////////////////////////

BOOL Dlg_OnInitDialog(HWND hWnd, HWND hWndFocus, LPARAM lParam) {

chSETDLGICONS(hWnd, IDI_FILECOPY);

Глава 10. Синхронный и асинхронный ввод-вывод на устройствах.docx 395

// Disable Copy button since no file is selected yet.

EnableWindow(GetDlgItem(hWnd, IDOK), FALSE); return(TRUE);

}

///////////////////////////////////////////////////////////////////////////////

void Dlg_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify) {

TCHAR szPathname[_MAX_PATH];

switch (id) { case IDCANCEL:

EndDialog(hWnd, id); break;

case IDOK:

// Copy the source file to the destination file. Static_GetText(GetDlgItem(hWnd, IDC_SRCFILE),

szPathname, _countof(szPathname)); SetCursor(LoadCursor(NULL, IDC_WAIT)); chMB(FileCopy(szPathname, TEXT("FileCopy.cpy"))

? "File Copy Successful" : "File Copy Failed"); break;

case IDC_PATHNAME:

OPENFILENAME ofn = { OPENFILENAME_SIZE_VERSION_400 }; ofn.hwndOwner = hWnd;

ofn.lpstrFilter = TEXT("*.*\0"); lstrcpy(szPathname, TEXT("*.*")); ofn.lpstrFile = szPathname; ofn.nMaxFile = _countof(szPathname);

ofn.lpstrTitle = TEXT("Select file to copy"); ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST; BOOL bOk = GetOpenFileName(&ofn);

if (bOk) {

// Show user the source file's size Static_SetText(GetDlgItem(hWnd, IDC_SRCFILE), szPathname); CEnsureCloseFile hFile = CreateFile(szPathname, 0, 0, NULL,

OPEN_EXISTING, 0, NULL); if (hFile.IsValid()) {

LARGE_INTEGER liFileSize; GetFileSizeEx(hFile, &liFileSize);

// NOTE: Only shows bottom 32 bits of size

SetDlgItemInt(hWnd, IDC_SRCFILESIZE, liFileSize.LowPart, FALSE);

396 Часть II. Приступаем к работе

}

}

EnableWindow(GetDlgItem(hWnd, IDOK), bOk); break;

}

}

///////////////////////////////////////////////////////////////////////////////

INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

switch (uMsg) {

chHANDLE_DLGMSG(hWnd, WM_INITDIALOG, Dlg_OnInitDialog); chHANDLE_DLGMSG(hWnd, WM_COMMAND, Dlg_OnCommand);

}

return(FALSE);

}

///////////////////////////////////////////////////////////////////////////////

int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR pszCmdLine, int) {

DialogBox(hInstExe, MAKEINTRESOURCE(IDD_FILECOPY), NULL, Dlg_Proc); return(0);

}

//////////////////////////////// End of File //////////////////////////////////

Оглавление

 

Г Л А В А 1 1

Пулы потоков...................................................................................................................

238

Сценарий 1. Асинхронный вызов функций......................................................................................

240

Явное управление рабочими элементами....................................................................................

241

Программа-пример Batch.................................................................................................................

243

Сценарий 2.

Вызов функций через определенные интервалы времени.....................................

247

Программа-пример TimedMsgBox ..................................................................................................

249

Сценарий 3.

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

253

Сценарий 4.

Вызов функций по завершении запросов асинхронного ввода-вывода ..............

256

Обработка завершения обратного вызова ...................................................................................

257

Настройка пула потоков...................................................................................................................

259

Корректное разрушение пула потоков и группы очистки...........................................................

261

Г Л А В А 1 1

Пулы потоков

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

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

Глава 11. Пулы потоков.docx 239

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

Эти функции дают возможность вызывать другие функции:

асинхронно;

через определенные промежутки времени;

при освобождении отдельных объектов ядра;

при завершении запросов асинхронного ввода-вывода.

Примечание. Первые API для управления пулами потоков появились в Windows 2000. В Windows Vista механизм пулов потоков был переработан, в результате появились новые API для управления пулами. Естественно Vista поддерживает прежние версии этих API для сохранения преемственной совместимости, однако в приложениях, предназначенных исключительно для Vista, рекомендуется использовать новые API В этой главе мы рассмотрим новые API-функции для управления пулами потоков в Windows Vista, информацию о прежних версиях API ищите в предыдущих изданиях этой книги.

240 Часть II. Приступаем к работе

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

Теперь, когда я вас предупредил, посмотрим, как все это работает.

Сценарий 1. Асинхронный вызов функций

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

VOID NTAPI SimpleCallback(

PTP_CALLBACK_INSTANCE plnstance, // См. раздел "Обработка завершения

// обратного вызова"

PVOID pvContext);

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

BOOL TrySubmitThreadpoolCallback( PTP_SIMPLE_CALLBACK pfnCallback, PVOID pvContext,

PTP_CALLBACK_ENVIRON pcbe); // См. раздел "Обработка завершения обрат вызова"

Эта функция добавляет рабочий элемент в очередь пула потоков (вызовом PostQueuedCompletionStatus) и возвращает TRUE, если операция завершится успешно, либо FALSE в противном случае. Функция TrySubmitThreadpoolCallback принимает несколько параметров. Параметр pfnCallback определяет вашу функцию, соответствующую прототипу SimpleCallback; параметр pvContext содержит аргументы вашей функции (значение ее параметра pvContext), а параметре PTPCALLBACKENVIRON можно передать просто NULL (подробнее об этом — в разделе о настройке пула потоков), параметр pInstance функции SimpleCallback я поясню позже (см. раздел «Обработка завершения обратного вызова»).

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

Глава 11. Пулы потоков.docx 241

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

Явное управление рабочими элементами

В некоторых ситуациях, таких как нехватка памяти, вызов TrySubmitThreadpoolCallback может закончиться неудачей. Это неприемлемо, если требуется координированное исполнение ряда действий, например, при отмене некоторой операции по таймеру. Устанавливая таймер, вы надеетесь, что инициированная им операция отмены будет обработана одним из потоков пула. Однако к моменту срабатывания таймера свободная память может быть исчерпана, и вызов TrySubmitThreadpoolCallback завершится неудачей. В подобных случаях вы должны самостоятельно создать объект «рабочий элемент» с таймером, чтобы впоследствии явно поставить его в очередь пула потоков.

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

PTP_W0RK CreateThreadpoolWork(

 

PTP_WORK_CALLBACK pfnWorkHandler,

 

PVOID pvContext,

 

PTP_CALLBACK_ENVIRON pcbe);

// см. раздел "Настройка пула потоков"

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

VOID CALLBACK WorkCallback(

PTP_CALLBACK_INSTANCE Instance,

PVOID Context,

PTP_W0RK Work);

Чтоб поставить запрос очередь пула потоков, вызовите функцию SubmitThreadpoolWork:

VOID SubmitThreadpoolWork(PTP_WORK pWork);

Теперь можно считать, что запрос поставлен в очередь успешно, и один из потоков пула исполнит функцию обратного вызова. Собственно, поэтому функция SubmitThreadpoolWork и возвращает значение типа VOID.

242 Часть II. Приступаем к работе

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

Если другому потоку требуется отменить «чужой» рабочий элемент либо приостановить свою работу до то завершения обработки некоторого рабочего элемента, можно вызвать следующую функцию:

VOID WaitForThreadpoolWorkCallbacks(

PTP_WORK pWork,

BOOL bCancelPendingCallbacks);

Параметр pWork — это указатель на рабочий элемент, ранее созданный и поставленный в очередь вызовами функций CreateThreadpoolWork и SubmitThreadpoolWork. Если заданный рабочий элемент еще не поставлен в очередь, WaitForThreadpoolWork Callbacks тут же возвращает управление, не выполняя никаких действий.

Если передать TRUE в параметре bCancelPendingCallbacks, функция WaitForThreadpoolWorkCallbacks попытается отменить рабочие элементы, ранее поставленные в очередь. Обработка текущего рабочего элемента не прерывается, WaitForThreadpoolWorkCallbacks дождется ее завершения, и только потом вернет управление. Если рабочий элемент уже поставлен в очередь, но еще не обработан, он помечается как отмененный и функция WaitForThreadpoolWorkCallbacks возвращает управление. Получив помеченный таким образом рабочий элемент, поток знает, что делать обратный вызов не нужно, и обработка этого элемента даже не начнется.

Если же передать FALSE в параметре bCancelPendingCallbacks, WaitForThread-poolWorkCallbacks приостановит вызывающий поток до завершения обработки заданного рабочего элемента и возврата в пул выполнявшего ее потока.

Примечание. Если в очередь поставлено несколько рабочих элементов с использованием одного и того же объекта PTP_WORK и при вызове WaitForThreadpoolWorkCallbacks передан параметр bCancelPendingCallbacks =

FALSE, эта функция будет ждать завершения обработки всех таких рабочих элементов. Если же в этом параметре передано значение TRUE, WaitForThreadpoolWorkCallbacks дождется завершения обработки только текущих элементов.

Рабочие элементы, ставшие ненужными, следует освободить вызовом функции CloseThreadpoolWork, принимающей единственный параметр — указатель на рабочий элемент:

VOID CloseThreadpoolWork(PTP_WORK pwk);

Глава 11. Пулы потоков.docx 243

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

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

Рис. 11-1. Вывод программы Batch

Исходный код и файлы ресурсов для этой программы находятся в каталоге 11Batch внутри архива, доступного на веб-сайте поддержки этой книги.

/******************************************************************************

Module: Batch.cpp

Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre

******************************************************************************/

#include "..\CommonFiles\CmnHdr.h" /* See Appendix A. */ #include <Windowsx.h>

#include <WinBase.h> #include <WinNT.h>

// C RunTime Header Files #include <stdlib.h> #include <malloc.h> #include <memory.h> #include <tchar.h> #include <strsafe.h>

#include "Batch.h"

//////////////////////////////////////////////////////////////////////////////

// Global variables HWND g_hDlg = NULL;

PTP_WORK g_pWorkItem = NULL; volatile LONG g_nCurrentTask = 0;

// Global definitions

#define WM_APP_COMPLETED (WM_APP+123)

//////////////////////////////////////////////////////////////////////////////

void AddMessage(LPCTSTR szMsg) {

244 Часть II. Приступаем к работе

HWND hListBox = GetDlgItem(g_hDlg, IDC_LB_STATUS);

ListBox_SetCurSel(hListBox, ListBox_AddString(hListBox, szMsg));

}

//////////////////////////////////////////////////////////////////////////////

void NTAPI TaskHandler(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work) {

LONG currentTask = InterlockedIncrement(&g_nCurrentTask);

TCHAR szMsg[MAX_PATH]; StringCchPrintf(

szMsg, _countof(szMsg),

TEXT("[%u] Task #%u is starting."), GetCurrentThreadId(), currentTask); AddMessage(szMsg);

// Simulate a lot of work Sleep(currentTask * 1000);

StringCchPrintf(

szMsg, _countof(szMsg),

TEXT("[%u] Task #%u is done."), GetCurrentThreadId(), currentTask);

AddMessage(szMsg);

if (InterlockedDecrement(&g_nCurrentTask) == 0)

{

// Notify the UI thread for completion.

PostMessage(g_hDlg, WM_APP_COMPLETED, 0, (LPARAM)currentTask);

}

}

//////////////////////////////////////////////////////////////////////////////

void OnStartBatch() {

// Disable Start button

Button_Enable(GetDlgItem(g_hDlg, IDC_BTN_START_BATCH), FALSE);

 

 

 

AddMessage(TEXT("

----Start a new batch----

"));

 

 

 

// Submit 4 tasks by using the same work item SubmitThreadpoolWork(g_pWorkItem);

Соседние файлы в предмете Программирование на C++