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? RegardsAnonymous
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!) mentionedAnonymous
June 04, 2009
When we started looking at how we could use the thread pool for asynchronous work, I (only!) mentioned