You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
781 lines
21 KiB
781 lines
21 KiB
//+-------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1992.
|
|
//
|
|
// File: thread.cxx
|
|
//
|
|
// Contents: Job scheduler thread code.
|
|
//
|
|
// Classes: CWorkerThread
|
|
// CWorkerThreadMgr
|
|
//
|
|
// Functions: RequestService
|
|
// WorkerThreadStart
|
|
//
|
|
// History: 25-Oct-95 MarkBl Created
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "..\pch\headers.hxx"
|
|
#pragma hdrstop
|
|
#include "debug.hxx"
|
|
|
|
#include "queue.hxx"
|
|
#include "task.hxx"
|
|
#include "jpmgr.hxx"
|
|
#include "globals.hxx"
|
|
#include "thread.hxx"
|
|
#include "proto.hxx"
|
|
|
|
extern "C"
|
|
HRESULT WorkerThreadStart(CWorkerThread *);
|
|
|
|
//
|
|
// Thread idle time before termination.
|
|
//
|
|
|
|
#define MAX_THREAD_IDLE_TIME (1000 * 60 * 3) // 3 minutes
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: WorkerThreadStart
|
|
//
|
|
// Synopsis: The entry point for a worker thread.
|
|
//
|
|
// Arguments: [pWrkThrd] -- Worker thread object to start.
|
|
//
|
|
// Returns: TBD
|
|
//
|
|
// Effects: Calls the StartWorking function for the provided worker
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
extern "C"
|
|
HRESULT WorkerThreadStart(CWorkerThread * pWrkThrd)
|
|
{
|
|
schDebugOut((DEB_USER3,
|
|
"WorkerThreadStart pWrkThrd(0x%x)\n",
|
|
pWrkThrd));
|
|
|
|
HRESULT hr;
|
|
|
|
//
|
|
// Call the worker thread class
|
|
//
|
|
|
|
hr = pWrkThrd->StartWorking();
|
|
|
|
delete pWrkThrd;
|
|
|
|
return(hr);
|
|
}
|
|
|
|
// Class CWorkerThread
|
|
//
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CWorkerThread::~CWorkerThread
|
|
//
|
|
// Synopsis: Destructor
|
|
//
|
|
// Arguments: N/A
|
|
//
|
|
// Returns: N/A
|
|
//
|
|
// Notes: A WorkerThread should only be deleted after its associated
|
|
// thread has been terminated. This is noted by having the
|
|
// _hThread being == NULL.
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
CWorkerThread::~CWorkerThread()
|
|
{
|
|
TRACE3(CWorkerThread, ~CWorkerThread);
|
|
|
|
// a race condition at shutdown may result in this dtor being called *after*
|
|
// the thread manager has been destructed.
|
|
if (gpThreadMgr)
|
|
gpThreadMgr->SignalThreadTermination();
|
|
|
|
//
|
|
// A precondition to destroying this thread is for the thread to
|
|
// have been terminated already. If this isn't the case, we are
|
|
// in big trouble.
|
|
//
|
|
// Terminating the thread is not good, since it leaves the threads
|
|
// stack allocated in our address space. We also are not sure what
|
|
// the thread is up to. It could have resources locked. But, we
|
|
// also don't know what it will do next. We will have no record of it.
|
|
//
|
|
|
|
if (_hThread == NULL && _hWorkAvailable != NULL)
|
|
{
|
|
CloseHandle(_hWorkAvailable);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// BUGBUG : Commenting out the assertion below until
|
|
// (ServiceStatus != STOPPING) can be added.
|
|
//
|
|
|
|
#if 0
|
|
schAssert(0 && (_hThread != NULL) &&
|
|
"Destroying CWorkerThread while thread exists. Memory Leak!");
|
|
#endif // 0
|
|
}
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CWorkerThread::AssignTask
|
|
//
|
|
// Synopsis: Assigns the task passed to this worker. A NULL task signals
|
|
// thread termination.
|
|
//
|
|
// Arguments: [pTask] -- Task to be serviced.
|
|
//
|
|
// Returns: S_OK
|
|
// E_FAIL -- Task already assigned.
|
|
// TBD
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
CWorkerThread::AssignTask(CTask * pTask)
|
|
{
|
|
schDebugOut((DEB_USER3,
|
|
"CWorkerThread::AssignTask(0x%x) pTask(0x%x)\n",
|
|
this,
|
|
pTask));
|
|
|
|
HRESULT hr;
|
|
|
|
//
|
|
// A must not already be assigned.
|
|
//
|
|
|
|
if (_pTask != NULL)
|
|
{
|
|
return(E_FAIL);
|
|
}
|
|
|
|
_pTask = pTask;
|
|
|
|
if (_pTask != NULL)
|
|
{
|
|
_pTask->AddRef();
|
|
}
|
|
|
|
//
|
|
// Signal the thread to process the task.
|
|
//
|
|
|
|
if (!SetEvent(_hWorkAvailable))
|
|
{
|
|
if (_pTask != NULL)
|
|
{
|
|
_pTask->Release();
|
|
_pTask = NULL;
|
|
}
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
return(hr);
|
|
}
|
|
|
|
return(S_OK);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CWorkerThread::Initialize
|
|
//
|
|
// Synopsis: Performs initialization unable to be performed in the
|
|
// constructor.
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Returns: S_OK
|
|
// TBD
|
|
//
|
|
// Effects: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
CWorkerThread::Initialize(void)
|
|
{
|
|
TRACE3(CWorkerThread, Initialize);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
//
|
|
// Create the event used to signal the thread to start working.
|
|
//
|
|
|
|
_hWorkAvailable = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
|
|
if (_hWorkAvailable == NULL)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
return(hr);
|
|
}
|
|
|
|
ULONG ThreadID;
|
|
_hThread = CreateThread(NULL,
|
|
WORKER_STACK_SIZE,
|
|
(LPTHREAD_START_ROUTINE)WorkerThreadStart,
|
|
this,
|
|
0,
|
|
&ThreadID);
|
|
|
|
if (_hThread != NULL)
|
|
{
|
|
if (!SetThreadPriority(_hThread, THREAD_PRIORITY_LOWEST))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
gpThreadMgr->SignalThreadCreation();
|
|
}
|
|
|
|
return(hr);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CWorkerThread::StartWorking
|
|
//
|
|
// Synopsis: The almost endless loop for a worker thread
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Returns: TBD
|
|
//
|
|
// Notes: This routine will sit in a loop, and wait for tasks to
|
|
// be assigned. When a task is assigned, it will call the
|
|
// PerformTask method of the task. If the assigned task
|
|
// is NULL, then the thread will kill itself.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
CWorkerThread::StartWorking(void)
|
|
{
|
|
TRACE3(CWorkerThread, StartWorking);
|
|
|
|
HRESULT hr = E_FAIL;
|
|
BOOL fTerminationSignalled = FALSE;
|
|
|
|
while (1)
|
|
{
|
|
//
|
|
// Wait on the work available semaphore for the signal that a task
|
|
// has been assigned.
|
|
//
|
|
|
|
DWORD dwRet = WaitForSingleObject(_hWorkAvailable,
|
|
MAX_THREAD_IDLE_TIME);
|
|
|
|
if (dwRet == WAIT_FAILED)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
break;
|
|
}
|
|
else if (dwRet == WAIT_TIMEOUT)
|
|
{
|
|
//
|
|
// This thread has timed out - see if it can be terminated.
|
|
// If this thread object exists in the global free thread
|
|
// pool, it can be terminated. If it doesn't exist in the pool,
|
|
// it means this thread is in use, therefore re-enter the wait.
|
|
//
|
|
// More detail on the above: It's possible another thread
|
|
// (thread A) can take this thread (thread B) for service just
|
|
// after thread B's wait has timed out. In absence of the thread
|
|
// pool check, thread B would exit out from under thread A.
|
|
//
|
|
|
|
CWorkerThread * pWrkThrd = gpThreadMgr->RemoveThread(_hThread);
|
|
|
|
if (pWrkThrd == NULL)
|
|
{
|
|
//
|
|
// This object doesn't exist in the pool. Assume another
|
|
// thread has taken this thread for service, re-enter the
|
|
// wait.
|
|
//
|
|
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This thread has expired.
|
|
//
|
|
// NB: DO NOT delete the thread object!
|
|
//
|
|
|
|
schAssert(pWrkThrd == this);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// A NULL task signals thread termination.
|
|
//
|
|
|
|
if (_pTask == NULL)
|
|
{
|
|
fTerminationSignalled = TRUE;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Perform the task.
|
|
//
|
|
|
|
_pTask->PerformTask();
|
|
|
|
_pTask->UnServiced();
|
|
|
|
//
|
|
// Release the task.
|
|
//
|
|
|
|
schDebugOut((DEB_USER3, "CWorkerThread::StartWorking(0x%x) "
|
|
"Completed and Releasing task(0x%x)\n",
|
|
this,
|
|
_pTask));
|
|
|
|
_pTask->Release();
|
|
_pTask = NULL;
|
|
|
|
//
|
|
// Return this thread to the global pool, if the service is not
|
|
// in the process of stopping. If the service is stopping, this
|
|
// thread must exit.
|
|
//
|
|
|
|
if (IsServiceStopping())
|
|
{
|
|
fTerminationSignalled = TRUE;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
gpThreadMgr->AddThread(this);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Scavenger duty. Perform global job processor pool housekeeping prior
|
|
// to thread termination.
|
|
//
|
|
// Do this only if the thread timed out; not when the thread is
|
|
// instructed to terminate.
|
|
//
|
|
|
|
if (!fTerminationSignalled)
|
|
{
|
|
gpJobProcessorMgr->GarbageCollection();
|
|
}
|
|
|
|
//
|
|
// By closing the handle, we allow the system to remove all remaining
|
|
// traces of this thread.
|
|
//
|
|
|
|
BOOL fTmp = CloseHandle(_hThread);
|
|
schAssert(fTmp && "Thread handle close failed - possible memory leak");
|
|
|
|
_hThread = NULL;
|
|
|
|
return(hr);
|
|
}
|
|
|
|
// Class CWorkerThreadMgr
|
|
//
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CWorkerThreadMgr::~CWorkerThreadMgr
|
|
//
|
|
// Synopsis: Destructor.
|
|
//
|
|
// Arguments: N/A
|
|
//
|
|
// Returns: None.
|
|
//
|
|
// Notes: Memory leaks can occur with this destructor. This destructor
|
|
// must only be called with process termination.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
CWorkerThreadMgr::~CWorkerThreadMgr()
|
|
{
|
|
TRACE3(CWorkerThreadMgr, ~CWorkerThreadMgr);
|
|
|
|
//
|
|
// This destructor must only be called with process termination.
|
|
//
|
|
// If the global thread count is non-zero, there are threads
|
|
// remaining. In this case, DO NOT delete the critical section or
|
|
// close the event handle! An active thread could still access it.
|
|
//
|
|
// This will be a memory leak on process termination if the count is
|
|
// non-zero, but the leak is far better than an a/v.
|
|
//
|
|
if (!_cThreadCount)
|
|
{
|
|
DeleteCriticalSection(&_csThreadMgrCritSection);
|
|
CloseHandle(_hThreadTerminationEvent);
|
|
}
|
|
else
|
|
{
|
|
schDebugOut((DEB_FORCE,
|
|
"CWorkerThreadMgr dtor(0x%x) : Unavoidable memory leak. " \
|
|
"Leaking one or more CWorkerThread objects since worker " \
|
|
"thread(s) are still active.\n",
|
|
this));
|
|
}
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CWorkerThreadMgr::AddThread
|
|
//
|
|
// Synopsis: Adds the worker thread indicated to the pool.
|
|
//
|
|
// Arguments: [pWrkThrd] -- Worker thread to add.
|
|
//
|
|
// Returns: None.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void
|
|
CWorkerThreadMgr::AddThread(CWorkerThread * pWrkThrd)
|
|
{
|
|
schDebugOut((DEB_USER3,
|
|
"CWorkerThreadMgr::AddThread(0x%x) pWrkThrd(0x%x)\n",
|
|
this,
|
|
pWrkThrd));
|
|
|
|
//
|
|
// If the service is stopping, instruct the thread to terminate; else,
|
|
// add it to the pool. Note, this is safe since only the threads
|
|
// themselves perform AddThread - the thread is free.
|
|
//
|
|
|
|
if (IsServiceStopping())
|
|
{
|
|
pWrkThrd->AssignTask(NULL);
|
|
return;
|
|
}
|
|
|
|
EnterCriticalSection(&_csThreadMgrCritSection);
|
|
|
|
_WorkerThreadQueue.AddElement(pWrkThrd);
|
|
|
|
LeaveCriticalSection(&_csThreadMgrCritSection);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CWorkerThreadMgr::Initialize
|
|
//
|
|
// Synopsis: Creates the private data member, _hThreadTerminationEvent.
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Returns: TRUE -- Creation succeeded;
|
|
// FALSE -- otherwise.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
BOOL
|
|
CWorkerThreadMgr::Initialize(void)
|
|
{
|
|
if ((_hThreadTerminationEvent = CreateEvent(NULL,
|
|
TRUE,
|
|
FALSE,
|
|
NULL)) == NULL)
|
|
{
|
|
schDebugOut((DEB_ERROR,
|
|
"CWorkerThreadMgr::Initialize(0x%x) CreateEvent failed, " \
|
|
"status = 0x%lx\n",
|
|
this,
|
|
GetLastError()));
|
|
return(FALSE);
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CWorkerThreadMgr::Shutdown
|
|
//
|
|
// Synopsis: This member is called once, no wait, early on in the schedule
|
|
// service's shutdown sequence. It relinquishes all free threads.
|
|
// Done so by signalling the threads in the pool to terminate.
|
|
//
|
|
// This member is called a second time, with the wait option, to
|
|
// ensure any busy (non-free) worker threads have terminated
|
|
// also.
|
|
//
|
|
// Arguments: [fWait] -- Flag instructing this method to wait indefinitely
|
|
// until all worker threads have terminated.
|
|
// ** Use only in this case of service stop **
|
|
//
|
|
// Returns: TRUE -- All worker threads terminated.
|
|
// FALSE -- One ore more worker threads still active.
|
|
//
|
|
// Notes: It must not be possible for worker threads to enter the
|
|
// thread pool critical section in their termination code paths.
|
|
// Otherwise, a nested, blocking section will result.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
BOOL
|
|
CWorkerThreadMgr::Shutdown(BOOL fWait)
|
|
{
|
|
#define THRDMGR_INITIAL_WAIT_TIME 2000 // 2 seconds.
|
|
|
|
EnterCriticalSection(&_csThreadMgrCritSection);
|
|
|
|
CWorkerThread * pWrkThrd;
|
|
|
|
while ((pWrkThrd = _WorkerThreadQueue.RemoveElement()) != NULL)
|
|
{
|
|
pWrkThrd->AssignTask(NULL); // NULL task signals termination.
|
|
}
|
|
|
|
LeaveCriticalSection(&_csThreadMgrCritSection);
|
|
|
|
//
|
|
// Optionally wait for outstanding worker threads to terminate before
|
|
// returning. This is only to be done during service shutdown. All worker
|
|
// threads have logic to terminate with service shutdown.
|
|
//
|
|
// To actually have to wait is a very rare. Only occurring in a rare
|
|
// case, or if this machine is under *extreme* loads, or the absolutely
|
|
// unexpected occurs. The rare case mentioned is a small window where
|
|
// a job processor object is spun up immediately prior to the service
|
|
// shutdown sequence and its initialization phase coincides with
|
|
// CJobProcessor::Shutdown().
|
|
//
|
|
|
|
if (fWait)
|
|
{
|
|
//
|
|
// On destruction, each thread decrements the global thread count
|
|
// and sets the thread termination event.
|
|
//
|
|
|
|
DWORD dwWaitTime = THRDMGR_INITIAL_WAIT_TIME;
|
|
DWORD dwRet;
|
|
|
|
while (_cThreadCount)
|
|
{
|
|
//
|
|
// Wait initially THRDMGR_INITIAL_WAIT_TIME amount of time.
|
|
// If this wait times-out, re-issue a Shutdown of the global
|
|
// processor object then wait infinitely.
|
|
//
|
|
|
|
if ((dwRet = WaitForSingleObject(_hThreadTerminationEvent,
|
|
dwWaitTime)) == WAIT_OBJECT_0)
|
|
{
|
|
ResetEvent(_hThreadTerminationEvent);
|
|
}
|
|
else if (dwRet == WAIT_TIMEOUT)
|
|
{
|
|
//
|
|
// Address the case where the job processor was spun up
|
|
// inadvertently. This will shut it down.
|
|
//
|
|
|
|
gpJobProcessorMgr->Shutdown();
|
|
dwWaitTime = INFINITE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This return code will notify the service cleanup code
|
|
// to not free up resources associated with the worker
|
|
// threads. Otherwise, they may fault.
|
|
//
|
|
|
|
return(FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CWorkerThreadMgr::RemoveThread
|
|
//
|
|
// Synopsis: Remove & return a thread from the pool.
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Returns: CWorkerThread * -- Returned thread.
|
|
// NULL -- Pool empty.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
CWorkerThread *
|
|
CWorkerThreadMgr::RemoveThread(void)
|
|
{
|
|
TRACE3(CWorkerThread, RemoveThread);
|
|
|
|
CWorkerThread * pWrkThrd = NULL;
|
|
|
|
EnterCriticalSection(&_csThreadMgrCritSection);
|
|
|
|
pWrkThrd = _WorkerThreadQueue.RemoveElement();
|
|
|
|
LeaveCriticalSection(&_csThreadMgrCritSection);
|
|
|
|
return(pWrkThrd);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CWorkerThreadMgr::RemoveThread
|
|
//
|
|
// Synopsis: Remove & return the thread from the pool with the associated
|
|
// handle.
|
|
//
|
|
// Arguments: [hThread] -- Target thread handle.
|
|
//
|
|
// Returns: CWorkerThread * -- Found it.
|
|
// NULL -- Worker thread not found.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
CWorkerThread *
|
|
CWorkerThreadMgr::RemoveThread(HANDLE hThread)
|
|
{
|
|
schDebugOut((DEB_USER3,
|
|
"CWorkerThreadMgr::RemoveThread(0x%x) hThread(0x%x)\n",
|
|
this,
|
|
hThread));
|
|
|
|
CWorkerThread * pWrkThrd;
|
|
|
|
EnterCriticalSection(&_csThreadMgrCritSection);
|
|
|
|
pWrkThrd = _WorkerThreadQueue.GetFirstElement();
|
|
|
|
while (pWrkThrd != NULL)
|
|
{
|
|
if (pWrkThrd->GetHandle() == hThread)
|
|
{
|
|
_WorkerThreadQueue.RemoveElement(pWrkThrd);
|
|
break;
|
|
}
|
|
|
|
pWrkThrd = pWrkThrd->Next();
|
|
}
|
|
|
|
LeaveCriticalSection(&_csThreadMgrCritSection);
|
|
|
|
return(pWrkThrd);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: RequestService
|
|
//
|
|
// Synopsis: Request a free worker thread from the global thread pool to
|
|
// service the task indicated. If no free threads exist in the
|
|
// pool, create a new one.
|
|
//
|
|
// Arguments: [pTask] -- Task to undergo service.
|
|
//
|
|
// Returns: S_OK
|
|
// E_OUTOFMEMORY
|
|
// TBD
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
RequestService(CTask * pTask)
|
|
{
|
|
schDebugOut((DEB_USER3, "RequestService pTask(0x%x)\n", pTask));
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
//
|
|
// Take no requests if the service is stopping.
|
|
//
|
|
|
|
if (IsServiceStopping())
|
|
{
|
|
schDebugOut((DEB_ERROR,
|
|
"RequestService pTask(0x%x) Service stopping - request " \
|
|
"refused\n",
|
|
pTask));
|
|
return(E_FAIL);
|
|
}
|
|
|
|
//
|
|
// Obtain a free thread from the global thread pool.
|
|
//
|
|
|
|
CWorkerThread * pWrkThrd = gpThreadMgr->RemoveThread();
|
|
|
|
if (pWrkThrd == NULL)
|
|
{
|
|
//
|
|
// Create a new worker thread object if none were available.
|
|
//
|
|
|
|
pWrkThrd = new CWorkerThread;
|
|
|
|
if (pWrkThrd == NULL)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
CHECK_HRESULT(hr);
|
|
return(hr);
|
|
}
|
|
|
|
hr = pWrkThrd->Initialize();
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
delete pWrkThrd;
|
|
return(hr);
|
|
}
|
|
}
|
|
|
|
hr = pWrkThrd->AssignTask(pTask);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
delete pWrkThrd;
|
|
}
|
|
|
|
return(hr);
|
|
}
|