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.