ThreadPool.BindHandle


I mentioned that we can use ThreadPool.BindHandle to implement asynchronous IO. Here are roughly the steps necessary to make it happen:


1.       Create an overlapped file handle


            SafeFileHandle handle = CreateFile(


                                filename,


                                Win32.GENERIC_READ_ACCESS,


                                Win32.FILE_SHARE_READ | Win32.FILE_SHARE_WRITE | Win32.FILE_SHARE_DELETE,


                                (IntPtr)null,


                                Win32.OPEN_EXISTING,


                                Win32.FILE_FLAG_OVERLAPPED,


                                new SafeFileHandle(IntPtr.Zero, false));


        [DllImport(“kernel32.dll”, CharSet = CharSet.Auto, SetLastError = true)]


        private static extern SafeFileHandle CreateFile(


           string lpFileName,


           uint dwDesiredAccess,


           uint dwShareMode,


            //SECURITY_ATTRIBUTES lpSecurityAttributes,


           IntPtr lpSecurityAttributes,


           uint dwCreationDisposition,


           int dwFlagsAndAttributes,


           SafeFileHandle hTemplateFile);


2.       Bind the handle to thread pool.


            if (!ThreadPool.BindHandle(handle))


            {


                Console.WriteLine(“Fail to BindHandle to threadpool.”);


                return;


        }


3.       Prepare your asynchronous IO callback.


                byte[] bytes = new byte[0x8000];


 


                IOCompletionCallback iocomplete = delegate(uint errorCode, uint numBytes, NativeOverlapped* _overlapped)


                {


                    unsafe


                    {


                        try


                        {


                            if (errorCode == Win32.ERROR_HANDLE_EOF)


                                Console.WriteLine(“End of file in callback.”);


 


                            if (errorCode != 0 && numBytes != 0)


                            {


                                Console.WriteLine(“Error {0} when reading file.”, errorCode);


                            }


                            Console.WriteLine(“Read {0} bytes.”, numBytes);


                        }


                        finally


                        {


                            Overlapped.Free(pOverlapped);


                        }


                    }


                };   


 


4.       Create a NativeOverlapped* pointer.


                    Overlapped overlapped = new Overlapped();


 


                    NativeOverlapped* pOverlapped = overlapped.Pack(iocomplete, bytes);


 


                pOverlapped->OffsetLow = (int)offset;


5.       Call the asynchronous IO API and pass the NativeOverlapped * to it.


                    fixed (byte* p = bytes)


                    {


                        r = ReadFile(handle, p, bytes.Length, IntPtr.Zero, pOverlapped);


                        if (r == 0)


                        {


                            r = Marshal.GetLastWin32Error();


                            if (r == Win32.ERROR_HANDLE_EOF)


                            {


                                Console.WriteLine(“Done.”);


                                break;


                            }


 


                            if (r != Win32.ERROR_IO_PENDING)


                            {


                                Console.WriteLine(“Failed to read file. LastError is {0}”, Marshal.GetLastWin32Error());


                                Overlapped.Free(pOverlapped);


                                return;


                            }


                        }


                    }


 


        [DllImport(“KERNEL32.dll”, SetLastError = true)]


        unsafe internal static extern int ReadFile(


            SafeFileHandle handle,


            byte* bytes,


            int numBytesToRead,


            IntPtr numBytesRead_mustBeZero,


            NativeOverlapped* overlapped);


 


Your IO callback will be invoked by CLR thread when the IO completed.


 


So when should you use ThreadPool.BindHandle? The answer is almost *Never*. .Net Framework’s FileStream class internally uses ThreadPool.BindHandle to implement the async IO. You should always use FileStream if possible.

Comments (8)

  1. In step 5 when you leave the fixed {} block the p is free to be moved by the GC. Doesn’t this buffer needs to be allocated using Marshall.MAlloc and freed after the read has finished?

    Regards

  2. JM says:

    @Jelle: you’re quite right. Using Marshal every time is slow, though. A better approach is to allocate a GCHandle with type GCHandleType.Pinned, which will prevent the GC from moving the array (and collecting it, incidentally).

  3. JM,

    Thanks for the pointer tot the GCHandle, hadn’t seen that class before. Till now I always try to allocate a buffer using Marshal.MAlloc and reuse it for multiple reads.

  4. In step 4,

                       NativeOverlapped* pOverlapped = overlapped.Pack(iocomplete, bytes);

    This statement implicitly pins the bytes object. The object is unpinned when the overlap is freed.

  5. junfeng,

    thanks for pointing it out, I must have somehow missed that while reading the documentation.

  6. LA.NET [EN] says:

    When we started looking at how we could use the thread pool for asynchronous work, I (only!) mentioned

  7. ASPInsiders says:

    When we started looking at how we could use the thread pool for asynchronous work, I (only!) mentioned