from system view - this is correct, safe, ok call CloseHandle at any time. this independed from are exist some I/O request in progress on file to which this handle pointed. even more call CloseHandle the best way (begin from vista, on xp - this is driver depended, usual I/O canceled in this case too) cancel all pending I/O requests
What happens if one thread closes the handle while another currently
has an I/O in progress
if this is not last handle to file - nothing hapen. otherwise (this is usual case and think you mean exactly this case - you have single handle open on file) the IRP_MJ_CLEANUP handler will be called in driver (which create device objec, on which your file created). then already is no general and common answer - driver specific. however most windows built-in drivers at this point just complete with some error (usual with STATUS_CANCELLED but not always, say npfs.sys (pipe driver) use STATUS_PIPE_BROKEN error here) all pending requests and return control. on xp/2003 - this is all. so this is driver duty complete all pending requests at this point. however some 3-th party drivers can and not do this - so some I/O can still be pending (undefined time) after handle closed. but begin from vista here was major change. the FILE_OBJECT was extended (on xp/2003 the CompletionContext was last field) - added IrpList. now inside FILE_OBJECT system mantain list of some active I/O request on file. when we send asynchronous IRP request it queued to Thread (always before vista) or to FILE_OBJECT - if IOCP port accosiated with this file object, ApcContext not 0 (from win32 view - pointer to OVERLAPPED not 0) and no user event in IRP (from win32 view OVERLAPPED.hEvent == 0). this affect when system cancel IRP - when thread exit or when last file handle closed. in vista, after IRP_MJ_CLEANUP handler return - system check IrpList and if it not empty - call IoCancelIrp for every Irp from this list. of course than again driver depended, but all well design drivers (including all microsoft drivers) when return pending for request (Irp) mandatory register CancelRoutine to be called if the specified IRP is canceled. this routine called when system call IoCancelIrp (if not removed before) and inside this routine driver complete request with STATUS_CANCELLED. this mean that if driver not complete Irp inside IRP_MJ_CLEANUP - system by self cancel this irp just after IRP_MJ_CLEANUP return and this cause that driver anyway complete this Irp inside it CancelRoutine
so conclusion - close last handle on file (last call CloseHandle ) is effective way for cancell all I/O requests on this file (with some exceptions on xp and only for 3-rd party drivers).this is legal, safe and effective (if we want cancell al io and close file)
Does CloseHandle block until the write finishes?
because inside this call called driver supplied IRP_MJ_CLEANUP handler - can be all. even block. but if say about built-in windows drivers - this never happens. and even for 3-rd party drivers - i never view this behaviour. so in practic - CloseHandle not block and not wait when all I/O requests on file finished
so call CloseHandle is safe at any time, but here exist another problem, not related to system design but related to your code design. in your code (and usual) handle is shared resourse - several different threads will use it. and this is usual problem, when object (handle in your case) used by several threads - who and when must close handle. what happens if one your thread close handle when another thread begin I/O request with this handle, after it was closed ? you or got STATUS_INVALID_HANDLE from api call, if handle not valid at this time. but possible and more bad case. after you close handle - some another code can create another object. and for new created object will be assigned the last closed handle ( system use stack model for free handles). as result handle can be already valid when you begin I/O operation (after close handle from another thread), but.. this handle already will point to another object. if this is not file object handle (say event or thread handle) - you got STATUS_OBJECT_TYPE_MISMATCH from I/O request. in whe worst case - this will be file object handle, but for another file. so you can begin I/O operation with another file and unpredicatable result (say write data to another, arbitrary file).
so you need somehow protect or synchronize shared handle usage and closing, between threads. first think here - use reference counting for handle and call CloseHandle when no more reference. but this is not usable on practic - if say some thread permanent do I/O on file(pipe) (say read from it, when read complete - just call another read and so on) - we have no chance call CloseHandle from another thread.
for concrete example we call ReadDirectoryChangesW for file asynchronous. when this call complete - just again call ReadDirectoryChangesW and so on. gow break this loop ? set some stop flag and call CancelIoEx ? but possible that CancelIoEx will be called in between 2 calls of ReadDirectoryChangesW, whe really no any I/O on file. so it nothing cancel.. effective way stop this - call CloseHandle, but if we do this without protection - exist risk that next call to ReadDirectoryChangesW will be use arbitrary object (handle value will be the same, but it will be invalid or point to another object). use reference counting - not way here - who permanent call ReadDirectoryChangesW need have own reference and reference never rich 0. so we never call CloseHandle and never break loop.
the effective and nice solution here - use Run-Down Protection for handle. unfortunatelly not api in user mode for support this, but not hard yourself implement this
possible usage (if implement exactly how this implemented in kernel)
threads before use m_hFile, protected by m_RunRef, do next code
ULONG dwError = ERROR_INVALID_HANDLE;
if (AcquireRundownProtection(&m_RunRef))
{
// use m_hFile in some I/O request
// for example
// dwError = ReadDirectoryChangesW(m_hFile, ..);
ReleaseRundownProtection(&m_RunRef);
}
thread which want close handle do next
WaitForRundownProtectionRelease(&m_RunRef);
CloseHandle(m_hFile);
note that WaitForRundownProtectionRelease wait not when all I/O complete but only when all another threads exit from AcquireRundownProtection / ReleaseRundownProtection block. also after call WaitForRundownProtectionRelease AcquireRundownProtection always return false - so prevent new acquire and what for release existing.
however i prefer another implementation for begin rundown:
asynchronous call to BeginRundown(&m_RunRef) (after which all new AcquireRundownProtection return false) - but not wait at this place. end when rundown completed - callback will be called. and inside this callback we safe call CloseHandle - no new usage of handle at this place, and all old usage of handle (in io call) already completed. note - usage of handle completed - not mean I/O completed - it in progress maybe. but handle need only for start I/O