File I/O on Microsoft Windows NT, Windows 2000,and Windows XP can be synchronous or asynchronous. The default behavior for I/O is synchronous: an I/O function is called and returns when the I/O is complete. Asynchronous I/O, on the other hand, allows an I/O function to return execution back to the caller immediately, but the I/O is not assumed to be complete until some future time. The operating system notifies the caller when the I/O is complete. Alternatively, the caller can determine the status of the outstanding I/O operation by using services of the operating system.
The advantage of asynchronous I/O is that the caller has time to do other work or issue more requests while the I/O operation is being completed. The term Overlapped I/O is frequently used for Asynchronous I/O and Non-overlapped I/O for Synchronous I/O.This article uses the terms Asynchronous and Synchronous for I/O operations under Windows NT.This article assumes the reader has certain familiarity with the File I/O functions such as CreateFile,ReadFile,WriteFile.
Frequently, asynchronous I/O operations behave just as synchronous I/O. Certain conditions that this article discusses in the later sections make the I/O operations complete synchronously. The caller has no time for background work because the I/O functions do not return until the I/O is complete.
Several functions are related to synchronous and asynchronous I/O.This article usesReadFileandWriteFile as examples; good alternatives would be ReadFileExandWriteFileEx. Although this article discusses only disk I/O specifically, many of the principles can be applied to other types of I/O, such as serial I/O or network I/O.
NOTE: Because Windows 95 does not support asynchronous I/O on disk devices (although it does on other types of I/O devices), its behavior is not covered inthis article.
Back to the top | Give Feedback
Collapse imageMore information
Set Up Asynchronous I/O
The FILE_FLAG_OVERLAPPED flag must be specified inCreateFile when the file is opened.This flag allows I/O operations on the file to be performed asynchronously. Here is an example:
HANDLE hFile;
hFile =CreateFile(szFileName,
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if(hFile == INVALID_HANDLE_VALUE)
ErrorOpeningFile();
Be careful when coding for asynchronous I/O because the system reserves the right to make an operation synchronous if it needs to. Therefore, it is best if you write the program to correctly handle an I/O operation that may be completed either synchronously or asynchronously. The sample code demonstrates this consideration.
There are many things a program can do while waiting for asynchronous operations to complete, such as queuing additional operations,or doing background work.For example, the following code properly handles overlapped and non-overlapped completion of a read operation. It does nothing more than waitfor the outstanding I/O to complete:
if(!ReadFile(hFile,
pDataBuf,
dwSizeOfBuffer,
&NumberOfBytesRead,
&osReadOperation )
{
if(GetLastError()!= ERROR_IO_PENDING)
{
// Some other error occurred while reading the file.
ErrorReadingFile();
ExitProcess(0);
}
else
// Operation has been queued and
// will complete in the future.
fOverlapped = TRUE;
}
else
// Operation has completed immediately.
fOverlapped = FALSE;
if(fOverlapped)
{
// Waitfor the operation to complete before continuing.
// You could do some background work if you wanted to.
if(GetOverlappedResult( hFile,
&osReadOperation,
&NumberOfBytesTransferred,
TRUE))
ReadHasCompleted(NumberOfBytesTransferred);
else
// Operation has completed, but it failed.
ErrorReadingFile();
}
else
ReadHasCompleted(NumberOfBytesRead);
Note that &NumberOfBytesRead passed intoReadFile is different from &NumberOfBytesTransferred passed intoGetOverlappedResult.If an operation has been made asynchronous, then GetOverlappedResult is used to determine the actual number of bytes transferred in the operation after it has completed. The &NumberOfBytesRead passed intoReadFile is meaningless.
If, on the other hand, an operation is completed immediately, then &NumberOfBytesRead passed intoReadFile is valid for the number of bytes read.Inthis case, ignore the OVERLAPPED structure passed intoReadFile; do not use it with GetOverlappedResultorWaitForSingleObject.
Another caveat with asynchronous operation is that you must not use an OVERLAPPED structure until its pending operation has completed.In other words,if you have three outstanding I/O operations, you must use three OVERLAPPED structures.If you reuse an OVERLAPPED structure, you will receive unpredictable results in the I/O operations and you may experience data corruption. Additionally, before you can use an OVERLAPPED structure for the first time,or before you reuse it after an earlier operation has completed, you must correctly initialize it so no left-over data affects the new operation.
The same typeof restriction applies to the data buffer used in an operation. A data buffer must not be read or written until its corresponding I/O operation has completed; reading or writing the buffer may cause errors and corrupted data.
Asynchronous I/O Still Appears to be Synchronous
If you followed the instructions earlier inthis article, however, all your I/O operations still typically complete synchronously in the order issued,and none of the ReadFile operations returns FALSE with GetLastError() returning ERROR_IO_PENDING,this means you have no time for any background work. Why does this occur?
There are a number of reasons why I/O operations complete synchronously even if you have coded for asynchronous operation:
Compression
One obstruction to asynchronous operation is NTFS compression. The file system driver will not access compressed files asynchronously; instead all operations are just made synchronous.This does not apply to files that are compressed with utilities similar to COMPRESS or PKZIP.
NTFS Encryption
Similar to Compression, file encryption causes the system driver to convert asynchronous I/O to synchronous.If the files are decrypted, the I/O requests will be asynchronous.
Extending a File
Another reason that I/O operations are completed synchronously is the operations themselves. On Windows NT, any write operation to a file that extends its length will be synchronous.
NOTE: Applications can make the previously mentioned write operation asynchronous by changing the Valid Data Lengthof the file by using the SetFileValidData function,and then issuing a WriteFile.
Using SetFileValidData (which is available on Windows XP and later versions), applications can efficiently extend files without incurring a performance penalty for zero-filling them.
Because the NTFS file system does not zero-fill the data up to the valid data length(VDL) that is defined by SetFileValidData,this function has security implications where the file may be assigned clusters that were previously occupied by other files. Therefore, SetFileValidData requires that the caller have the new SeManageVolumePrivilege enabled (by default,this is assigned only to administrators). Microsoft recommends that ISVs carefully consider the implications of using this function.
Cache
Most I/O drivers (disk, communications,and others) have special case code where,if an I/O request can be completed "immediately," the operation will be completed and the ReadFileorWriteFile function will return TRUE.In all ways, these types of operations appear to be synchronous.For a disk device, typically, an I/O request can be completed "immediately" when the data is cached in memory.
Data Is notin Cache
The cache scheme can work against you, however,if the data is notin the cache. The Windows NT cache is implemented internally using file mappings. The memory manager in Windows NT does not provide an asynchronous page fault mechanism to manage the file mappings used by the cache manager. The cache manager can, however, verify whether the requested page is in memory, so if you issue an asynchronous cached read,and the pages are notin memory, the file system driver assumes that you do not want your thread blocked and the request will be handled by a limited pool of worker threads. Control is returned to your program after your ReadFilecall with the read still pending.
This works fine for a small number of requests, but because the pool of worker threads is limited (currently three on a 16MB system), there will still be only a few requests queued to the disk driver at a particular time.If you issue a lot of I/O operations for data that is notin the cache, the cache manager and memory manager become saturated and your requests are made synchronous.
The behavior of the cache manager can also be influenced based on whether you access a file sequentially or randomly. Benefits of the cache are seen most when accessing files sequentially. The FILE_FLAG_SEQUENTIAL_SCAN flag in the CreateFilecall will optimize the cache forthistypeof access. However,if you access files in a random fashion, use the FILE_FLAG_RANDOM_ACCESS flag inCreateFile to instruct the cache manager to optimize its behavior for random access.
Do not Use the Cache
The FILE_FLAG_NO_BUFFERING flag has the most effect on the behavior of the file system for asynchronous operation.This is the best way to guarantee that I/O requests are actually asynchronous. It instructs the file system to not use any cache mechanism at all.
WARNING: There are some restrictions to using this flag that have to do with the data buffer alignment and the device