Share via


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

  • Anonymous
    December 01, 2008
    PingBack from http://blog.a-foton.ru/index.php/2008/12/01/threadpoolbindhandle/

  • Anonymous
    December 02, 2008
    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

  • Anonymous
    December 02, 2008
    @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).

  • Anonymous
    December 03, 2008
    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.

  • Anonymous
    December 03, 2008
    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.

  • Anonymous
    December 04, 2008
    junfeng, thanks for pointing it out, I must have somehow missed that while reading the documentation.

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

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