Scenario 4: Call Functions When Asynchronous IO Requests Complete

[Previous] [Next]

The last scenario is a common one: your server application issues some asynchronous I/O requests. When these requests complete, you want to have a pool of threads ready to process the completed I/O requests. This is the architecture that I/O completion ports were originally designed for. If you were managing your own thread pool, you would create an I/O completion port and create a pool of threads that wait on this port. You would also open a bunch of I/O devices and associate their handles with the completion port. As asynchronous I/O requests complete, the device drivers would queue the "work items" to the completion port.

This is a great architecture that allows for a few threads to efficiently handle several work items, and it's fantastic that the thread pooling functions have this built in, saving you a lot of time and effort. To take advantage of this architecture, all you have to do is open your device and associate it with the non-I/O component of the thread pool. Remember that the non-I/O component's threads all wait on an I/O completion port. To associate a device with this component, you call this function:

 BOOL BindIoCompletionCallback( HANDLE hDevice, POVERLAPPED_COMPLETION_ROUTINE pfnCallback, ULONG dwFlags); 

Internally, this function calls CreateIoCompletionPort, passing it hDevice and the handle of the internal completion port. Calling BindIoCompletionCallback also guarantees that at least one thread is always in the non-I/O component. The completion key associated with this device is the address of the overlapped completion routine. This way, when I/O to this device completes, the non-I/O component knows which function to call so that it can process the completed I/O request. The completion routine must have the following prototype:

 VOID WINAPI OverlappedCompletionRoutine( DWORD dwErrorCode, DWORD dwNumberOfBytesTransferred, POVERLAPPED pOverlapped); 

You'll notice that you do not pass an OVERLAPPED structure to BindIoCompletionCallback. The OVERLAPPED structure is passed to functions such as ReadFile and WriteFile. The system keeps track of this overlapped structure internally with the pending I/O request. When the request completes, the system places the address of the structure in the completion port so that it can be passed to your OverlappedCompletionRoutine. Also, because the address of the completion routine is the completion key, to get additional context information into the OverlappedCompletionRoutine function, you should use the traditional trick of placing the context information at the end of the OVERLAPPED structure.

You should also be aware that closing a device causes all of its pending I/O requests to complete immediately with an error code. Be prepared to handle this in your callback function. If, after closing the device, you want to make sure that no callbacks are executed, you must do reference counting in your application. In other words, you must increment a counter every time you issue an I/O request and decrement the counter each time an I/O request completes.

Currently, there are no special flags that you can pass to BindIoCompletionCallback s dwFlags parameter, so you must pass 0. I believe that one flag you should be able to pass is WT_EXECUTEINIOTHREAD. If an I/O request completes, this gets queued to a non-I/O component thread. In your OverlappedCompletionRoutine function, you ll probably issue another asynchronous I/O request. But remember that if a thread that issues I/O requests terminates, the I/O requests are also destroyed. Also, the threads in the non-I/O component are created or destroyed depending on the workload. If the workload is low, a thread in this component might terminate with outstanding I/O requests pending. If BindIoCompletionCallback supported the WT_EXECUTEINIOTHREAD flag, a thread waiting on the completion port would wake up and post the result to an I/O component thread. Since these threads never die if any I/O requests are pending, you could issue I/O requests without the fear of them being destroyed.

While the WT_EXECUTEINIOTHREAD flag would be nice, you can easily emulate the behavior I just described. In your OverlappedCompletionRoutine function, you simply call QueueUserWorkItem, passing the WT_EXECUTEINIOTHREAD flag and whatever data you need (at least the overlapped structure, probably). This is all that the thread pooling functions would do for you anyway.



Programming Applications for Microsoft Windows
Programming Applications for Microsoft Windows (Microsoft Programming Series)
ISBN: 1572319968
EAN: 2147483647
Year: 1999
Pages: 193

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net