线程池实现Word下载.docx
《线程池实现Word下载.docx》由会员分享,可在线阅读,更多相关《线程池实现Word下载.docx(41页珍藏版)》请在冰豆网上搜索。
ExtThreadPool.plg723
ExtThreadPool.rc3,242
ExtThreadPool.rc2405
Resource.h322
StdAfx.cpp215
StdAfx.h669
ThreadPool.cpp3,130
ThreadPool.h863
线程池使用WindowsNT和Windows2000操作系统系列下的可用完成端口。
这种线程池的基本原理是创建一个单独的完成端口,使所有的工作线程在此完成端口上等待,当新请求进入时,将该请求发送至此完成端口。
完成端口机制可确保只有一个控制线程获得这个新请求的通知。
如果不使用完成端口而使用其他同步对象(如信号灯),也可以创建并管理线程池。
但是,使用完成端口时,操作系统会以最有效的方式调度和释放线程(即“后进先出”)。
在典型的ISAPI扩展中,“ISAPIMFC向导”将创建两个虚函数:
GetExtensionVersion和TerminateExtension。
完成端口的初始化是在GetExtensionVersion中通过调用CreateIoCompletionPort来完成的。
请注意,使用此函数的最后一个参数
,您可以指定可同时从线程池中运行的线程数。
最有效的方法是使运行的线程数与CPU数相同。
最后一个参数如果是0,则表示允许操作系统选取与CPU数相匹配的线程数。
BOOLCExtThreadPoolExtension:
:
GetExtensionVersion(
HSE_VERSION_INFO*pVer)
{
//Calldefaultimplementationforinitialization
CHttpServer:
GetExtensionVersion(pVer);
//Loaddescriptionstring
TCHARsz[HSE_MAX_EXT_DLL_NAME_LEN+1];
ISAPIVERIFY(:
LoadString(AfxGetResourceHandle(),
IDS_SERVER,sz,HSE_MAX_EXT_DLL_NAME_LEN));
_tcscpy(pVer-&
gt;
lpszExtensionDesc,sz);
m_phThreads=newHANDLE[THREAD_COUNT];
m_nThreadCount=THREAD_COUNT;
m_hIoPort=CreateIoCompletionPort((HANDLE)INVALID_HANDLE_VALUE,
NULL,0,0);
//NOTE:
IfaCWinAppobjectisnotdeclare,thiscallshouldbe
//changedtoCreateThread()instead.
for(longn=0;
n&
lt;
m_nThreadCount;
n++)
CWinThread*pWinThread=AfxBeginThread(
(AFX_THREADPROC)ThreadProc,m_hIoPort);
m_phThreads[n]=pWinThread-&
m_hThread;
}
returnTRUE;
此处,在CExtThreadPoolExtension类中定义了三个变量:
m_nThreadCount、m_hIoPort和m_phThreads。
m_nThreadCount是长整型,用于定义在线程池中创建的线程数(在ThreadPool.h文件中将该值硬编码为THREAD_COUNT变量)。
m_hIoPort是单独的完成端口句柄,您应将其发送给所有工作线程进行等待。
m_phThreads是来自工作线程的线程句柄数组。
应当注意的是,本示例是在共享库中使用MFC构建的。
如果使用向导将MFCISAPI扩展创建为静态链接库,则CWinApp的实例将不可用。
因此,AfxBeginThread()调用应当替换为CreateThread()。
虽然“MFCISAPI向导”创建了GetExtensionVersion和TerminateExtension方法,但是还需要手动替代CHttpServer的HttpExtensionProc()方法。
这可以通过在CExtThreadPoolExtension声明中添加该方法来完成:
classCExtThreadPoolExtension:
publicCHttpServer
public:
CExtThreadPoolExtension();
~CExtThreadPoolExtension();
//Overrides
//ClassWizardgeneratedvirtualfunctionoverrides
//NOTE-theClassWizardwilladdandremovemember
//functionshere.
//DONOTEDITwhatyouseeintheseblocksofgeneratedcode!
//{{AFX_VIRTUAL(CExtThreadPoolExtension)
virtualBOOLGetExtensionVersion(HSE_VERSION_INFO*pVer);
virtualDWORDHttpExtensionProc(EXTENSION_CONTROL_BLOCK*pECB);
//}}AFX_VIRTUAL
virtualBOOLTerminateExtension(DWORDdwFlags);
//TODO:
Addhandlersforyourcommandshere.
//Forexample:
voidDefault(CHttpServerContext*pCtxt);
DECLARE_PARSE_MAP()
//{{AFX_MSG(CExtThreadPoolExtension)
//}}AFX_MSG
protected:
HANDLE*m_phThreads;
HANDLEm_hIoPort;
longm_nThreadCount;
};
这是必须的,因为必须替代CHttpServer:
HttpExtensionProc()返回的值,以使IIS知道您仍在处理此请求,而不回收EXTENSION_CONTROL_BLOCK。
替代HttpExtensionProc的代码相当简单:
DWORDCExtThreadPoolExtension:
HttpExtensionProc(
EXTENSION_CONTROL_BLOCK*pECB)
//Theresultofthebaseclass&
#39;
s(CHttpServer)HttpExtensionProc()
//mustbechecked.Ifit&
ssuccessful,youwillneedtoreturna
//HSE_STATUS_PENDING.ThisissothatIISwillnotshutdownthe
//connectionandnotdestroytheECB.
//Iftheresultofthebaseclassisnotsuccessful,youshould
//simplypropagatetheerrormessageuptoIIS.
DWORDdwResult=CHttpServer:
HttpExtensionProc(pECB);
if(dwResult==HSE_STATUS_SUCCESS)
returnHSE_STATUS_PENDING;
else
returndwResult;
只须检查从CHttpServer:
HttpExtensionProc()中返回的调用,如果成功,则返回pending。
否则,仅传送出现的错误。
在Default()实现(或在消息映射中MFCISAPI扩展调用的任一例程)中,提取EXTENSION_CONTROL_BLOCK(请注意,这与CHttpServerContext对象不同;
不能将CHttpServerContext对象传递给工作线程,因为该对象仅存在于CHttpServer:
HttpExtensionProc()作用域内),并将其投递到在GetExtensionVersion()中创建的完成端口:
voidCExtThreadPoolExtension:
Default(CHttpServerContext*pCtxt)
if(!
PostQueuedCompletionStatus(m_hIoPort,
(DWORD)pCtxt-&
m_pECB,0,NULL))
charszBuffer[4096]={0};
sprintf(szBuffer,&
quot;
&
H1&
Errorpostingtocompletionport!
Win32Error=%i&
/H1&
GetLastError());
DWORDdwBufLen=strlen(szBuffer);
pCtxt-&
m_pECB-&
WriteClient(pECB-&
ConnID,szBuffer,
amp;
dwBufLen,0);
如果完成端口投递失败,则需要生成一条错误消息,并将其重新报告给客户端。
请注意,要输出这一错误,请从ECB(而不是MFC包装程序)中直接使用WriteClient回调。
此外,本示例还演示将各种参数传递给工作线程的方法。
除了Default()外,还有ProcessName()和ProcessSample()。
ProcessName()采用三个字符串参数:
Firstname、Lastname和Middlename。
ProcessSample采用两个参数:
Data和String。
Data将是整型数据,String是字符串型数据。
其目的是利用MFCISAPI扩展的分析映射支持,并允许工作线程处理适当的请求。
要实现此目的,需创建以下两种结构:
structProcessNameData
EXTENSION_CONTROL_BLOCK*pECB;
CStringsFirstName;
CStringsLastName;
CStringsMiddleName;
structProcessSampleData
DWORDnValue;
CStringsString;
ProcessNameData结构将用于临时存放由MFC分析映射传递给ProcessName()函数的参数。
在ProcessName()函数内,动态分配一个新的ProcessNameData结构,并设置EXTENSION_CONTROL_BLOCK以及传递给我们的参数,然后将其传递给工作线程。
但是,将数据传递给工作线程时,必须使工作线程知道所传递的数据为ProcessNameData类型。
这可通过PostQueuedCompletionStatus()API中的第三个参数来实现。
在Default()函数中,将0作为第三个参数进行传递。
对于ProcessName(),传递的是1:
ProcessName(CHttpServerContext*pCtxt,
LPCTSTRszFirstName,
LPCTSTRszLastName,
LPCTSTRszMiddleName)
ProcessNameData*pData=newProcessNameData;
pData-&
pECB=pCtxt-&
m_pECB;
sFirstName=szFirstName;
sLastName=szLastName;
sMiddleName=szMiddleName;
PostQueuedCompletionStatus(m_hIoPort,(DWORD)pData,
(DWORD)1,NULL))
sprintf(szBuffer,
Win32Error=%i&
GetLastError());
WriteClient(pCtxt-&
通过将值1传递给工作线程,工作线程将知道它需要处理的数据与Default()例程不同。
同样,对于ProcessSample()函数传递值2。
在此函数中,应创建一个新的ProcessSampleData结构实例。
在ThreadProc(工作线程所执行的实际例程)中,需要使所有线程阻塞在一个单独的完成端口(从GetExtensionVersion传递给线程例程)。
当线程调用GetQueuedCompletionStatus()时将结束这种状况。
如果未投递任何状态,则调用将一直阻塞,直至投递一个状态为止。
投递完成端口后,其中的一个工作线程将“醒来”并开始处理:
unsignedintThreadProc(void*p)
HANDLEIoPort=(HANDLE*)p;
unsignedlongpN1,pN2;
OVERLAPPED*pOverLapped;
while(GetQueuedCompletionStatus(IoPort,&
pN1,&
pN2,
pOverLapped,INFINITE))
if(pOverLapped==(OVERLAPPED*)0xFFFFFFFF)
break;
switch(pN2)
case0:
DoWork((EXTENSION_CONTROL_BLOCK*)pN1);
case1:
DoWork((ProcessNameData*)pN1);
case2:
DoWork((ProcessSampleData*)pN1);
default:
//Huh?
!
?
return0;
可以看到,在switch()语句中,对正确的pN2值(该值是在PostQueuedCompletionStatus()调用中传递的第三个参数)进行了测试。
另外,还需要指出的是,DoWork()存在三个版本。
一个版本将采用EXTENSION_CONTROL_BLOCK指针,另一个版本采用ProcessNameData指针,而第三个版本采用ProcessSampleData指针。
通过显式地强制转换pN1,可以确保为提供的数据调用了正确的版本。
对于采用ProcessNameData和ProcessSampleData的DoWork()版本,需要在退出之前破坏这些结构。
这是因为这些结构创建的目的只是用于临时存放分析映射参数:
voidDoWork(ProcessNameData*pData)
EXTENSION_CONTROL_BLOCK*pECB=pData-&
pECB;
CStringsBody;
//BuildtheHTMLbody...
sBody.Format(
Thisisthread%iinthreadpool.Hithere!
GetCurrentThreadId());
sBody+=&
HR&
H2&
I&
mprocessingthisfor&
;
sBody+=pData-&
sFirstName;
&
if(pData-&
sMiddleName.CompareNoCase(&
~&
)!
=0)
sMiddleName;
sLastName;
/H2&
//Pretendwe&
respendingsometimeprocessingthisrequest
//thissampleassumes3seconds.
Sleep(3000);
SendData(pECB,sBody);
deletepData;
voidDoWork(ProcessSampleData*pData)
CStringsBody;
Thisisthesampledata:
sString;
CStringsTmp;
sTmp.Format(&
(%i)&
pData-&
nValue);
sBody+=sTmp;
SendData()是一个将CString发送回客户端的简单函数。
但是,在开始处理之前,需要确保没有向线程发出终止信号。
这可通过测试pOverLapped指针的值来完成。
如果该重叠指针的值是0xFFFFFFFF(无效的句柄值),则说明线程将终止。
因此,例程将跳出while循环。
要向工作线程发出终止信号,可使用通过“MFCISAPI向导”创建的第三个虚函数:
TerminateExtension()。
在此函数中,需要向所有线程发出关闭信号,同时关闭完成端口:
BOOLCExtThread
PoolExtension:
TerminateExtension(DWORDdwFlags)
//extensionisbeingterminated
PostQueuedCompletionStatus(m_hIoPort,0,
0,(OVERLAPPED*)0xFFFFFFFF);
WaitForMultipleObjects(m_nThreadCount,m_phThreads,TRUE,120000);
CloseHandle(m_hIoPort);
delete[]m_phThreads;
要向所有线程发出关闭信号,可使用无效句柄值投递一个重叠指针。
对可用线程数执行此操作。
这将确保每个线程都收到该通知。
完成此操作后,通过对从AfxBeginThread()调用返回的句柄(这些句柄复制自