Click here to Skip to main content
15,881,204 members
Articles / Programming Languages / C++
Article

One use for Overlapped I/O

Rate me:
Please Sign up or sign in to vote.
4.81/5 (56 votes)
14 Jan 2004CPOL4 min read 168.1K   105   19
How to use overlapped I/O

Introduction

If you’re like me you’ve taken one look at the Microsoft documentation on overlapped I/O and concluded that you don’t really need it. After all, for the vast majority of the applications I’ve had to work on, I/O processing goes something like this:

C++
OpenFile()

While (File.HasData())
{
    ReadData();
    ProcessData();
    WriteData();
}

CloseFile()

Typically you don’t need to setup I/O operations to allow processing and reading/writing to overlap. Even if you did you could achieve similar results using multithreading. So why did Microsoft provide overlapping I/O? Well here’s one use for it.

Details

Let’s use named pipes instead of real files for our I/O. Let’s further assume we’re writing a server application, which will handle one or more instances of a named pipe. (See Conclusions below for why I used a named pipe in this example).

This is one (greatly simplified and not error checked) way to write the loop. I’m assuming a global BOOL variable called gbl_bStop initially set to FALSE.

C++
while (!gbl_bStop)
{
    HANDLE h = CreateNamedPipe();

    if (ConnectNamedPipe(h))
        _beginthread(threadProc, 0, h);
}

This loop runs until gbl_bStop is set to TRUE. It creates a named pipe and then sits inside the ConnectNamedPipe() call waiting for a client to connect. When a client connects the ConnectNamedPipe() call returns and a new thread is launched to handle this particular client/server connection. The handle returned in the CreateNamedPipe() call is passed to the thread procedure, which will presumably use the handle for read or write operations.

Typically the above loop would be running in its own thread so that your application can handle other events.

This code works fine until you want to stop the server. The main thread sets gbl_bStop to TRUE and waits in vain for the thread to terminate. The problem is that very little of the threads time is spent executing code over which you have any control. Most of the time it’s sitting inside that ConnectNamedPipe() call, deep inside the Operating System, waiting for client connections.

Aha, we can work around that. Let’s set gbl_bStop to TRUE and then make a connection (from our main thread) to the named pipe. Well that will work but it also starts up yet another thread. Ok, we can work around that by checking gbl_bStop when we come out of the ConnectNamedPipe() call to see if we should continue or exit.

Now our loop looks like this:

C++
while (!gbl_bStop)
{
    HANDLE h = CreateNamedPipe();

    if (ConnectNamedPipe(h) && !gbl_bStop)
        _beginthread(threadProc, 0, h);
}

Not much added code but don’t forget our shutdown routine also has to create a connection to the named pipe. It’s getting somewhat complex, with code needed to shut down our application scattered all over the place.

Overlapped I/O usage

This is where overlapped I/O comes in. Let’s write our loop like this instead. I’m assuming a global EVENT object called gbl_hStopEvent and I’m still leaving out a lot of detail.

C++
OVERLAPPED  op;
HANDLE      h,
            handleArray[2];
BOOL        bStop = FALSE;
		
memset(&op, 0, sizeof(op));
handleArray[0] = op.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
handleArray[1] = gbl_hStopEvent;
		
while (bStop == FALSE)
{
    h = CreateNamedPipe(FILE_FLAG_OVERLAPPED);

    ConnectNamedPipe(h, &op);
				
    switch (WaitForMultipleObjects(2, handleArray, FALSE, INFINITE))
    {
    case WAIT_OBJECT_0:
        _beginthread(threadProc, 0, h);
        ResetEvent(handleArray[0]);
        break;

    case WAIT_OBJECT_0 + 1:
        CloseHandle(h);
        bStop = TRUE;
        break;
    }
}

CloseHandle(handleArray[0]);

The key to this is the WaitForMultipleObjects(). We create an array containing a handle to the global stop event and a handle to a local event. The local event handle is also used in the OVERLAPPED structure. We create the named pipe using overlapped I/O and then call ConnectNamedPipe(). However, because we specified overlapped I/O the Operating System returns control to us immediately and we fall into the WaitForMultipleObjects() call. If a client connects to the named pipe the OS will signal the event handle we passed in the OVERLAPPED structure and the code for WAIT_OBJECT_0 is executed. If, on the other hand, our main thread signals gbl_hStopEvent the code for WAIT_OBJECT_0 + 1 is executed and we will exit the loop entirely.

The same technique can be used for the thread procedure using the named pipe connection and for the client side code.

Conclusions

I chose named pipes for this example for simplicity (the examples require fewer lines of code compared to a socket example). But the choice of something other than a regular file on the disk was deliberate. When reading a disk file you're working with a known and predictable entity. On the large time scale (the time scale humans experience) a read from a disk file takes very little time and it's easy to determine when the operation has reached the end (end of file). Thus the pseudocode I showed at the start of the article is reasonable. But when dealing with interprocess communications across (possibly) network connections it's not possible to determine with any kind of accuracy just when the operation might end.

An obvious (and wrong) solution might be to write a tight loop polling the named pipe for new data. But, given the asynchronous nature of a named pipe data conduit you run a risk of wasting an awful lot of CPU cycles executing that tight loop.

Overlapped I/O provides an easy way to ensure your application is always responsive to user input without consuming excessive amounts of CPU time. Code that doesn't run costs the least in performance.

History

  • 19 December 2002 - First version.

  • 15 January 2004 - Added some comments about why Named Pipes were used in the pseudocode.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
I've been programming for 35 years - started in machine language on the National Semiconductor SC/MP chip, moved via the 8080 to the Z80 - graduated through HP Rocky Mountain Basic and HPL - then to C and C++ and now C#.

I used (30 or so years ago when I worked for Hewlett Packard) to repair HP Oscilloscopes and Spectrum Analysers - for a while there I was the one repairing DC to daylight SpecAns in the Asia Pacific area.

Afterward I was the fourth team member added to the Australia Post EPOS project at Unisys Australia. We grew to become an A$400 million project. I wrote a few device drivers for the project under Microsoft OS/2 v 1.3 - did hardware qualification and was part of the rollout team dealing directly with the customer.

Born and bred in Melbourne Australia, now living in Scottsdale Arizona USA, became a US Citizen on September 29th, 2006.

I work for a medical insurance broker, learning how to create ASP.NET websites in VB.Net and C#. It's all good.

Oh, I'm also a Kentucky Colonel. http://www.kycolonels.org

Comments and Discussions

 
GeneralMy vote of 5 Pin
AndyLau00429-Aug-13 18:14
AndyLau00429-Aug-13 18:14 
GeneralThanks! Pin
Duojiao Yan8-Dec-11 20:44
Duojiao Yan8-Dec-11 20:44 
GeneralHere's another way... Pin
Paul Sanders (the other one)2-Mar-08 7:21
Paul Sanders (the other one)2-Mar-08 7:21 
If you go back to your original, simple, loop, there's a way to get the loop to exit when you want it to without having to resort to overlapped IO. Two ways, actually.

The simplest is simply to close the handle in another thread. ConnectNamedPipe will then return with an error condition. Alternatively, connect to the named pipe yourself from another thread (having first set gbl_bStop TRUE, of course). Again, this 'unblocks' the waiting thread.

I use the 'CloseHandle' trick in connection with WinSock. This enables me to write a 'communications' thread using simple blocking I/O, and a 'user interface' thread which closes the socket if the user clicks 'Cancel'.


GeneralRe: Here's another way... Pin
jjshean20-Nov-09 9:21
jjshean20-Nov-09 9:21 
Questionwhich windows versions support it ?? Pin
pratibhap6-Feb-08 1:49
pratibhap6-Feb-08 1:49 
AnswerRe: which windows versions support it ?? Pin
Rob Manderson6-Feb-08 3:46
protectorRob Manderson6-Feb-08 3:46 
GeneralGood paper around a bad desing Pin
darbal20-Jan-04 2:21
darbal20-Jan-04 2:21 
GeneralRe: Good paper around a bad desing Pin
edsela20-Jan-04 3:32
edsela20-Jan-04 3:32 
GeneralRe: Good paper around a bad desing Pin
armentage21-Jan-04 7:10
armentage21-Jan-04 7:10 
GeneralExcellent ! But i have some problems. Pin
guit444-May-03 22:00
guit444-May-03 22:00 
GeneralRe: Excellent ! But i have some problems. Pin
ignacio14-Feb-05 10:25
ignacio14-Feb-05 10:25 
Generalalso for sockets Pin
  Forogar  2-Apr-03 3:34
professional  Forogar  2-Apr-03 3:34 
GeneralThanks very much Pin
Paul Farry17-Mar-03 12:10
professionalPaul Farry17-Mar-03 12:10 
GeneralRe: Thanks very much Pin
Rob Manderson17-Mar-03 12:38
protectorRob Manderson17-Mar-03 12:38 
GeneralRe: Thanks very much Pin
Paul Farry17-Mar-03 18:55
professionalPaul Farry17-Mar-03 18:55 
GeneralHelped Me Pin
Renjith Ramachandran23-Jan-03 7:37
Renjith Ramachandran23-Jan-03 7:37 
Generala good read... Pin
adrian cooper20-Dec-02 5:14
adrian cooper20-Dec-02 5:14 
GeneralNice Pin
Giles19-Dec-02 22:58
Giles19-Dec-02 22:58 
GeneralFinally.. Overlapped I/O explained PinPopular
Vadim Tabakman19-Dec-02 12:48
Vadim Tabakman19-Dec-02 12:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.