本章内容
11.1 情形1: 以异步方式调用函数
11.2 情形2:每隔一段时间调用一个函数
11.3 情形3:在内核对象触发时调用一个函数
11.4 情形4:在异步I/O请求完成时调用一个函数
11.5 回调函数的终止操作
Windows系统的线程池函数允许我们做这些事情:
1)以异步方式来调用一个函数
2)每隔一段时间调用一个函数
3)当内核对象触发的时候调用一个函数
4)当异步I/O请求完成时调用一个函数
默认情况当一个进程创建的时候,它并没有与任何线程池有关的开销。若调用了新的线程池函数,系统就会为进程创建相应的内核资源,其中一些资源在进程终止之前都将一直存在。
为了使用线程池来以异步方式执行一个函数,需要定义一个具有以下原型的函数
VOID NTAPI SimpleCallback(
PTP_CALLBACK_INSTANCE pInstance,
PVOID pvContext);
然后为了让线程池中的一个线程执行该函数,需要向线程池提交一个请求。为了达到这个目的,我们只需要调用下面函数。
WINBASEAPI
_Must_inspect_result_
BOOL
WINAPI
TrySubmitThreadpoolCallback(
_In_ PTP_SIMPLE_CALLBACK pfns,
_Inout_opt_ PVOID pv,
_In_opt_ PTP_CALLBACK_ENVIRON pcbe
);
该函数(通过调用PostQueuedCompletionStatus)来将一个工作项(work item)添加到线程池的队列中,若调用成功,则返回TRUE,若失败则返回FALSE
pfns 前面申明的那个原型函数
pv 对应原型函数的pvContext参数,会传递给原型函数。
pcbe 传递NULL
注意:不需要调用CreateThread来创建线程池,系统会自动为我们创建线程池,并自动分配线程池中的线程来调用我们的回调函数。
此外当线程处理完一个请求以后,不会立即销毁,而是回到线程池,准备好处理队列中的其他工作项。
线程池会不断重复使用其中的线程,而不会频繁的创建和销毁。
当然某些情况下创建新的线程,某些情况下会销毁线程(线程池算法)
某些情况下TrySubmitThreadpoolCallback可能会失败。
当多个操作需要互相协调的时候(比如一个计时器需要依靠一个工作项来取消另一个操作的时候)这是不能接受的。
每次调用TrySubmitThreadpoolCallback的时候,系统内部会以我们的名义分配一个工作项。如果打算提交大量工作项,那么处于性能和内存考虑,创建一个工作项一次,然后分多次提交会更好。调用下面函数来创建一个工作项:
WINBASEAPI
_Must_inspect_result_
PTP_WORK
WINAPI
CreateThreadpoolWork(
_In_ PTP_WORK_CALLBACK pfnwk,
_Inout_opt_ PVOID pv,
_In_opt_ PTP_CALLBACK_ENVIRON pcbe
);
这个函数会在用户模式中创建一个结构来保存它的三个参数,并返回指向该结构的指针。
pfnwk 是一个函数指针,被线程池中的函数调用。
pv 传给回调函数的参数
pcbe 设置为NULL
PTP_WORK_CALLBACK原型
VOID CALLBACK WorkCallback(
PTP_CALLBACK_INSTANCE hInstance,
PVOID Context,
PTP_WORK work);
当我们想要想线程池提交一个请求的时候,可以调用SubmitThreadpoolWork函数
WINBASEAPI
VOID
WINAPI
SubmitThreadpoolWork(
_Inout_ PTP_WORK pwk
);
现在已经可以假定将工作项成功添加到队列了。
如果有另一个线程,该线程想要取消已经提交的工作项,或者该线程由于要等待工作项处理完毕而需要将自己挂起,那么可以调用下面函数:
WINBASEAPI
VOID
WINAPI
WaitForThreadpoolWorkCallbacks(
_Inout_ PTP_WORK pwk,
_In_ BOOL fCancelPendingCallbacks
);
如果该工作项未被提交,那么该函数将立即返回而不执行任何操作。
如果传递TRUE 给fCancelPendingCallbacks函数,那么WaitForThreadpoolWorkCallbacks会试图取消之前提交的那个工作项。
如果该工作项已经被线程池中的线程进行处理,那么该工作并不会被打断。WaitForThreadpoolWorkCallbacks会等待其处理完毕再返回。
如果该工作项尚未被任何线程处理,那么他会被取消并理解返回。(当完成端口取出该工作项的时候,线程池知道无需调用函数,这样该工作项根本不会被执行)
如果fCancelPendingCallbacks传递FALSE,那么该函数会将线程挂起知道指定的工作项处理完成。
不再需要一个工作项的时候,应该调用CloseThreadpoolWork并传入其指针将其释放。
WINBASEAPI
VOID
WINAPI
CloseThreadpoolWork(
_Inout_ PTP_WORK pwk
);
Batch展示了如何使用线程池的工作项来实现对多个操作进行批处理,每个操作会通知用户界面该操作的状态。并以执行该操作的线程的表示符为前缀。
源代码
/******************************************************************************
Module: Batch.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/
#include "..\CommonFiles\CmnHdr.h"
#include
// C RunTime Header Files
#include
#include
#include
#include
#include
#include "resource.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) {
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);
SubmitThreadpoolWork(g_pWorkItem);
SubmitThreadpoolWork(g_pWorkItem);
SubmitThreadpoolWork(g_pWorkItem);
AddMessage(TEXT("4 tasks are submitted."));
}
//////////////////////////////////////////////////////////////////////////
void Dlg_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify) {
switch (id) {
case IDOK:
case IDCANCEL:
EndDialog(hWnd, id);
break;
case IDC_BTN_START_BATCH:
OnStartBatch();
break;
}
}
BOOL Dlg_OnInitDialog(HWND hWnd, HWND hWndFocus, LPARAM lParam) {
chSETDLGICONS(hWnd, IDI_MY10BATCH);
// Keep track of main dialog window for error messages
g_hDlg = hWnd;
return TRUE;
}
//////////////////////////////////////////////////////////////////////////
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);
case WM_APP_COMPLETED:
{
TCHAR szMsg[MAX_PATH + 1];
StringCchPrintf(
szMsg, _countof(szMsg),
TEXT("____Task #%u was the last task of the batch____"), lParam);
// Don't forget to enable the button
Button_Enable(GetDlgItem(hWnd, IDC_BTN_START_BATCH), TRUE);
}
break;
}
return FALSE;
}
//////////////////////////////////////////////////////////////////////////
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR pCmdLine, int) {
// Create the work item that will be used by all tasks
g_pWorkItem = CreateThreadpoolWork(TaskHandler, NULL, NULL);
if (g_pWorkItem == NULL) {
MessageBox(NULL, TEXT("Impossible to create the work item for tasks."),
TEXT(""), MB_ICONSTOP);
}
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, Dlg_Proc,
_ttoi(pCmdLine));
// Dont't forget to delete the work item
CloseThreadpoolWork(g_pWorkItem);
return 0;
}
Windows提供了可等待计时器内核对象,它使我们非常容易就能够得到一个基于时间的通知。
线程池封装了可等待计时器可以供我们直接使用。
为了将一个工作项安排在某个时间执行,定义一个回调函数
VOID CALLBACK TimeoutCallback(
PTP_CALLBACK_INSTANCE pInstance,
PVOID pvContext,
PTP_TIMER pTimer);
WINBASEAPI
_Must_inspect_result_
PTP_TIMER
WINAPI
CreateThreadpoolTimer(
_In_ PTP_TIMER_CALLBACK pfnti,
_Inout_opt_ PVOID pv,
_In_opt_ PTP_CALLBACK_ENVIRON pcbe
);
pfnti 是前面定义的回调函数
pv 是传递给回调函数的参数
pcbe设置为NULL
成功会返回一个计时器对象。
向线程池注册计时器采用以下函数
WINBASEAPI
VOID
WINAPI
SetThreadpoolTimer(
_Inout_ PTP_TIMER pti,
_In_opt_ PFILETIME pftDueTime,
_In_ DWORD msPeriod,
_In_opt_ DWORD msWindowLength
);
pTimer 表示由CreateThreadpoolTimer创建返回的计时器对象
pftDueTime 第一次调用回调函数应该是什么时候(可以传一个负值来指定一个相对时间 但-1表示立即开始)
如果使用绝对时间应该以100纳秒为单位。从1600年1月1日开始计算。(参考第九章可等待定时器)
如果只想让计时器触发一次,可以传msPeriod 为0
如果要让线程池定期调用,那么传递一个非0 值,表明周期(单位是微妙)
msWindowLength 用来给回调函数的执行时间增加一些随机性,这使得回调函数会被之前设定的触发时间加上msWindowLength设定的时间触发。
(防止多个计时器冲突)
另外线程池调度算法会优化触发值相近的回调函数,避免大量的context切换浪费系统资源。
另外在设置了计时器以后可以再次调用SetThreadpoolTimer并传入之前的计时器对象对其进行修改。
也可以传入pftDueTime = 0 表明停止计时器调用TimerCallback函数
可以调用IsThreadpoolTimerSet来确定某个计时器是否已经被设置(也就是说,它的pftDueTime值不为NULL)
WINBASEAPI
BOOL
WINAPI
IsThreadpoolTimerSet(
_Inout_ PTP_TIMER pti
);
最后可以调用WaitForThreadpoolTimerCallbacks来让线程等待一个计时器完成,还可以调用
CloseThreadpoolTimer来释放计时器的内存。
Timed Message Box示例程序
/******************************************************************************
Module: TimedMsgBox.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/
#include "..\CommonFiles\CmnHdr.h"
#include
#include
//////////////////////////////////////////////////////////////////////////
// The caption of our message box
TCHAR g_szCaption[100];
// How many second we'll display the message box
int g_nSecLeft = 0;
// This is STATIC window control ID for a message box
#define ID_MSGBOX_STATIC_TEXT 0x0000ffff
//////////////////////////////////////////////////////////////////////////
VOID CALLBACK MsgBoxTimeoutCallback(
PTP_CALLBACK_INSTANCE pInstance,
PVOID pvContext,
PTP_TIMER pTimer
)
{
// NOTE: Due to a thread race condition, it is possible (but very unlikely)
// that the message box will not be created when we get here.
HWND hwnd = FindWindow(NULL, g_szCaption);
if (hwnd != NULL) {
if (g_nSecLeft == 1) {
// The time is up; force the message box to exit.
EndDialog(hwnd, IDOK);
return;
}
// The window does exist; update the time remaining.
TCHAR szMsg[100];
StringCchPrintf(szMsg, _countof(szMsg),
TEXT("Your have %d seconds to respond"), --g_nSecLeft);
SetDlgItemText(hwnd, ID_MSGBOX_STATIC_TEXT, szMsg);
}
else {
// The window does not exist yet; do nothing this time.
// We 'll try again in another second.
}
}
int WINAPI _tWinMain(HINSTANCE, HINSTANCE, PTSTR, int) {
_tcscpy_s(g_szCaption, _countof(g_szCaption), TEXT("Timed MEssage Box"));
// How many seconds we'll give the user to respond
g_nSecLeft = 10;
// Create the threadpool timer object
PTP_TIMER lpTimer =
CreateThreadpoolTimer(MsgBoxTimeoutCallback, NULL, NULL);
if (lpTimer == NULL) {
TCHAR szMsg[MAX_PATH];
StringCchPrintf(szMsg, _countof(szMsg),
TEXT("Impossible to create the timer: %u"), GetLastError());
MessageBox(NULL, szMsg, TEXT("Error"), MB_OK || MB_ICONERROR);
return -1;
}
// Start the timer in one second to trigger every 1 second
ULARGE_INTEGER ulRelativeStartTime;
ulRelativeStartTime.QuadPart = (LONGLONG)-(10000000); // start in 1 second
FILETIME ftRelativeStartTime;
ftRelativeStartTime.dwHighDateTime = ulRelativeStartTime.HighPart;
ftRelativeStartTime.dwLowDateTime = ulRelativeStartTime.LowPart;
SetThreadpoolTimer(
lpTimer,
&ftRelativeStartTime,
1000, // Triggers every 1000 milliseconds
0);
// Display the message box
MessageBox(NULL, TEXT("You have 10 seconds to respond"),
g_szCaption, MB_OK);
// Clean up the timer
CloseThreadpoolTimer(lpTimer);
// Let us know if the user responed or if we timed out
MessageBox(
NULL, (g_nSecLeft == 1) ? TEXT("Timeout") : TEXT("User responded"),
TEXT("REsult"), MB_OK);
return 0;
}
线程池可能会调用多个线程来调用我们的回调函数(要做好线程同步)
如果线程池超负荷运行,可能会延误计时器的工作项。(比如线程池最大线程数设定为一个很低的值,那么线程池将不得不延误调用我们的回调函数)
如果工作想好时间较长,又不喜欢上诉行为,希望在每个工作项开始运行后的10秒钟将新的工作项添加到队列中,那么必须通过另一种途径来构造一种智能的一次性计时器。
1)仍然通过CreateThreadpoolTimer来创建计时器
2)调用SetThreadpoolTimer时给msPeriod参数传递0,表明这是一次性计时器
3)当处理工作完成后,重置计时器,仍然将msPeriod设为0
4)最后当最终需要停止计时器的时候,必须在CloseThreadpoolTimer执行前调用WaitForThreadpoolTimerCallbacks并传TRUE给最后一个参数,告知线程池不应该为该计时器处理任何工作项。
注意:真的需要一次性计时器则应该在回调函数中调用SetThreadpoolTimer并穿0给msPeriod, 同时为了清理线程池中的资源在回调函数中调用CloseThreadpoolTimer
可以创建一个工作项,让他在一个内核对象被触发的时候执行。首先创建一个回调函数
VOID CALLBACK WaitCallback(
PTP_CALLBACK_INSTANCE pInstance,
PVOID Context,
PTP_WAIT Wait,
TP_WAIT_RESULT WaitREsult);
WINBASEAPI
_Must_inspect_result_
PTP_WAIT
WINAPI
CreateThreadpoolWait(
_In_ PTP_WAIT_CALLBACK pfnwa,
_Inout_opt_ PVOID pv,
_In_opt_ PTP_CALLBACK_ENVIRON pcbe
);
接着就可以调用一下函数来讲一个内核对象绑定给这个等待对象并提交给线程池
WINBASEAPI
VOID
WINAPI
SetThreadpoolWait(
_Inout_ PTP_WAIT pwa,
_In_opt_ HANDLE h,
_In_opt_ PFILETIME pftTimeout
);
h是等待的内核对象。
pftTimeout用来表示线程池最长应该花多少时间来等待内核对象触发。 0表示不用等待, 负表示相对时间,正数表示绝对等待时间。
线程池在内部会调用WaitForMultipleObjects函数,传入通过SetThreadpoolWait函数注册的句柄,并穿FALSE给bWaitAll参数。
这样单任何一个句柄被触发的时候,线程池就会被唤醒。
由于WaitForMultipleObjects不允许多次传入同一个句柄,因此要确保SetThreadpoolWait不会注册同一个句柄。
但是可以调用DuplicateHandle,这样就可以为原始句柄创建副本。
当内核对象触发或等待超时的时候,线程池中的一个线程就会调用我们的WaitCallback函数。
最后一个WaitResult 类型为TP_WAIT_RESULT表明WaitCallback被调用的原因。
一旦线程池调用了注册的回调函数,对应的等待项将进入不活跃状态(inactive)。如果想让同一个回调函数在内核对象再次触发时候被调用需要调用
SetThreadpoolWait再次注册
另外SetThreadpoolWait再可以将同一个等待项和不同内核对象绑定。(在前一个内核对象已经触发以后)
也可以传入NULL将等待项从线程池中移出。
可以调用WaitForThreadpoolWaitCallbacks函数来等待一个等待项完成。
调用ClosethreadpoolWait来释放一个等待项的内存。
编写一个符合以下原型的函数
VOID CALLBACK OverlappedCompletionRoutine(
PTP_CALLBACK_INSTANCE pInstance,
PVOID pvContext,
PVOID POverlapped,
ULONG IoResult,
ULONG_PTR NumberOfBytesTransferred,
PTP_IO pIo);
当IO完成以后,这个函数会表调用并得到一个指向OVERLAPPED结构的指针(在调用ReadFile或者WrieFile时候传入)
操作结果通过IoResult传入如果IO成功,该参数为NO_ERROR.
以传输字节通过NumberOfBytesTransferred传入
pIO是一个指向线程池中的IO项指针
然后通过调用CreateThraedpoolIo来创建一个线程池IO对象,并将我们想要与线程池内部的IO完成端口相关联的文件句柄传入。
WINBASEAPI
_Must_inspect_result_
PTP_IO
WINAPI
CreateThreadpoolIo(
_In_ HANDLE fl,
_In_ PTP_WIN32_IO_CALLBACK pfnio,
_Inout_opt_ PVOID pv,
_In_opt_ PTP_CALLBACK_ENVIRON pcbe
);
WINBASEAPI
VOID
WINAPI
StartThreadpoolIo(
_Inout_ PTP_IO pio
);
在发出IO请求后让线程池停止调用回调函数:
WINBASEAPI
VOID
WINAPI
CancelThreadpoolIo(
_Inout_ PTP_IO pio
);
当文件设备使用完成以后调用CloseHandle将其关闭
WINBASEAPI
VOID
WINAPI
CloseThreadpoolIo(
_Inout_ PTP_IO pio
);
WINBASEAPI
VOID
WINAPI
WaitForThreadpoolIoCallbacks(
_Inout_ PTP_IO pio,
_In_ BOOL fCancelPendingCallbacks
);
线程池提供了一种便利的方法来描述在我们回调函数返回之后,应该执行的操作。采用pInstance参数(原型为 PTP_CALLBACK_INSTANCE)
可能调用一下参数:
WINBASEAPI
VOID
WINAPI
LeaveCriticalSectionWhenCallbackReturns(
_Inout_ PTP_CALLBACK_INSTANCE pci,
_Inout_ PCRITICAL_SECTION pcs
);
WINBASEAPI
VOID
WINAPI
ReleaseMutexWhenCallbackReturns(
_Inout_ PTP_CALLBACK_INSTANCE pci,
_In_ HANDLE mut
);
WINBASEAPI
VOID
WINAPI
ReleaseSemaphoreWhenCallbackReturns(
_Inout_ PTP_CALLBACK_INSTANCE pci,
_In_ HANDLE sem,
_In_ DWORD crel
);
WINBASEAPI
VOID
WINAPI
SetEventWhenCallbackReturns(
_Inout_ PTP_CALLBACK_INSTANCE pci,
_In_ HANDLE evt
);
WINBASEAPI
VOID
WINAPI
FreeLibraryWhenCallbackReturns(
_Inout_ PTP_CALLBACK_INSTANCE pci,
_In_ HMODULE mod
);
还有两个函数可以用于回调函数的实例
WINBASEAPI
BOOL
WINAPI
CallbackMayRunLong(
_Inout_ PTP_CALLBACK_INSTANCE pci
);
WINBASEAPI
VOID
WINAPI
DisassociateCurrentThreadFromCallback(
_Inout_ PTP_CALLBACK_INSTANCE pci
);
2)第二个函数通知线程池当前工作项逻辑上已经完成,所有由于调用WaitForThreadpoolWorkCallbacks WaitForThreadpoolTimerCallbacks, WaitForThreadpoolWaitCallbacks或WaitForThreadpoolIoCalbacks而被阻塞的线程能够早一点返回,不必等到线程池的回调函数返回
在创建CreateThreadpoolxxx的时候会传入一个PTP_CALLBACK_ENVIRON参数
如果传入NULL会把工作项添加到默认的线程池中。
可以自己创建并定制线程池
WINBASEAPI
_Must_inspect_result_
PTP_POOL
WINAPI
CreateThreadpool(
_Reserved_ PVOID reserved
);
会返回一个线程池相关的PTP_POOL的值,然后就可以对线程池进行定制。
WINBASEAPI
BOOL
WINAPI
SetThreadpoolThreadMinimum(
_Inout_ PTP_POOL ptpp,
_In_ DWORD cthrdMic
);
WINBASEAPI
VOID
WINAPI
SetThreadpoolThreadMaximum(
_Inout_ PTP_POOL ptpp,
_In_ DWORD cthrdMost
);
当应用程序不再需要为自己定制线程池时,调用CloseThreadpool将其销毁
WINBASEAPI
VOID
WINAPI
CloseThreadpool(
_Inout_ PTP_POOL ptpp
);
线程池队列中所有未处理的项目将被取消。
一旦创建了自己的线程池,并指定了线程的最小数量和最大数量,就可以初始化一个回调环境(callback environment)。
typedef struct _TP_CALLBACK_ENVIRON_V3 {
TP_VERSION Version;
PTP_POOL Pool;
PTP_CLEANUP_GROUP CleanupGroup;
PTP_CLEANUP_GROUP_CANCEL_CALLBACK CleanupGroupCancelCallback;
PVOID RaceDll;
struct _ACTIVATION_CONTEXT *ActivationContext;
PTP_SIMPLE_CALLBACK FinalizationCallback;
union {
DWORD Flags;
struct {
DWORD LongFunction : 1;
DWORD Persistent : 1;
DWORD Private : 30;
} s;
} u;
TP_CALLBACK_PRIORITY CallbackPriority;
DWORD Size;
} TP_CALLBACK_ENVIRON_V3;
typedef TP_CALLBACK_ENVIRON_V3 TP_CALLBACK_ENVIRON, *PTP_CALLBACK_ENVIRON;
InitializeThreadpoolEnvironment(
_Out_ PTP_CALLBACK_ENVIRON pcbe
);
VOID
DestroyThreadpoolEnvironment(
_Inout_ PTP_CALLBACK_ENVIRON pcbe
);
将线程池环境和自定义线程池绑定
VOID
SetThreadpoolCallbackPool(
_Inout_ PTP_CALLBACK_ENVIRON pcbe,
_In_ PTP_POOL ptpp
);
可以调用SetThreadpoolCallbackRunsLong函数来告诉回调环境,工作项需要较长时间处理。
VOID
SetThreadpoolCallbackRunsLong(
_Inout_ PTP_CALLBACK_ENVIRON pcbe
);
VOID
SetThreadpoolCallbackLibrary(
_Inout_ PTP_CALLBACK_ENVIRON pcbe,
_In_ PVOID mod
);
对于自定义线程池,可以使用清理组来正确销毁。
1)首先创建一个清理组
WINBASEAPI
_Must_inspect_result_
PTP_CLEANUP_GROUP
WINAPI
CreateThreadpoolCleanupGroup(
VOID
);
然后将清理组合一个已经绑定了线程池的回调环境关联起来
VOID
SetThreadpoolCallbackCleanupGroup(
_Inout_ PTP_CALLBACK_ENVIRON pcbe,
_In_ PTP_CLEANUP_GROUP ptpcg,
_In_opt_ PTP_CLEANUP_GROUP_CANCEL_CALLBACK pfng
);
pfng是一个回调函数地址。原型是
VOID CALLBACK CleanupGroupCancelCallback(
PVOID pvObjectContext,
PVOID pvCleanupContext);
如果调用CloseThreadpoolXXX 则是隐式将对应项从清理组中移出。
也可以显示地释放所有清理组中添加的项
WINBASEAPI
VOID
WINAPI
CloseThreadpoolCleanupGroupMembers(
_Inout_ PTP_CLEANUP_GROUP ptpcg,
_In_ BOOL fCancelPendingCallbacks,
_Inout_opt_ PVOID pvCleanupContext
);
然后会调用之前SetThreadpoolCallbackCleanupGroup所传递的回调函数。没一个工作项都会调用一次回调函数。
如果传递FALSE给fCancelPendingCallbacks那么回答函数并不会被调用。
在所有工作项被取消或处理以后调用CloseThreadpoolCleanupGroup来释放清理组所占用的资源
WINBASEAPI
VOID
WINAPI
CloseThreadpoolCleanupGroup(
_Inout_ PTP_CLEANUP_GROUP ptpcg
);
VOID
DestroyThreadpoolEnvironment(
_Inout_ PTP_CALLBACK_ENVIRON pcbe
);
WINBASEAPI
VOID
WINAPI
CloseThreadpool(
_Inout_ PTP_POOL ptpp
);