Minimal reproducible example:
Interop.cs
public static class Interop
{
[DllImport("kernel32.dll")]
public static extern IntPtr CreateIoCompletionPort(
[In] IntPtr fileHandle,
[In] IntPtr existingCompletionPort,
[In] UInt32 completionKey,
[In] UInt32 numberOfConcurrentThreads);
[DllImport("kernel32.dll")]
public static extern UInt32 GetLastError();
[DllImport("kernel32.dll")]
public static unsafe extern bool GetQueuedCompletionStatus(
[In] IntPtr completionPort,
[Out] out UInt32 ptrBytesTransferred,
[Out] out UInt32 ptrCompletionKey,
[Out] NativeOverlapped** lpOverlapped,
[In] UInt32 dwMilliseconds);
[DllImport("kernel32.dll")]
public static extern IntPtr CreateFile(
[In] string fileName,
[In] UInt32 dwDesiredAccess,
[In] UInt32 dwShareMode,
[In] IntPtr lpSecurityAttributes,
[In] UInt32 dwCreationDisposition,
[In] UInt32 dwFlagsAndAttributes,
[In] IntPtr hTemplateFile);
[DllImport("kernel32.dll")]
public static unsafe extern bool ReadFile(
[In] IntPtr hFile,
[Out] byte[] lpBuffer,
[In] uint maxBytesToRead,
[Out] out UInt32 bytesActuallyRead,
[In] NativeOverlapped* lpOverlapped);
[DllImport("kernel32.dll")]
public static extern bool PostQueuedCompletionStatus(
[In] IntPtr completionPort,
[In] UInt32 bytesTrasferred,
[In] UInt32 completionKey,
[In] IntPtr lpOverlapped);
}
Program.cs
class Program
{
static unsafe void Main(string[] args)
{
// create completion port
var completionPortHandle = Interop.CreateIoCompletionPort(new IntPtr(-1), IntPtr.Zero, 0, 0);
ThreadLogger.Log("Completion port handle: {0}", completionPortHandle);
var completionPortThread = new Thread(() => new IOCompletionWorker().Start(completionPortHandle))
{
IsBackground = true
};
completionPortThread.Start();
const uint Flags = 128 | (uint)1 << 30;
var fileHandle = Interop.CreateFile("test.txt", (uint)1 << 31, 0, IntPtr.Zero, 3,
/*FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED */ Flags,
IntPtr.Zero);
ThreadLogger.Log("File handle: {0}", fileHandle);
Interop.CreateIoCompletionPort(
fileHandle,
completionPortHandle,
(uint)fileHandle.ToInt64(),
0);
ThreadLogger.Log("Associated file handle with completion port");
var readBuffer = new byte[1024];
uint bytesRead;
var overlapped = new Overlapped
{
AsyncResult = new FileReadAsyncResult()
{
ReadCallback = (bytesCount, buffer) =>
{
var contentRead = Encoding.UTF8.GetString(buffer, 0, (int)bytesCount);
ThreadLogger.Log(contentRead);
},
Buffer = readBuffer
}
};
NativeOverlapped* nativeOverlapped = overlapped.UnsafePack((uint errorCode, uint numBytes, NativeOverlapped* pOVERLAP) =>
{
ThreadLogger.Log("Why am I not getting printed?");
}, readBuffer);
ThreadLogger.Log("Before read in main thread");
Interop.ReadFile(fileHandle, readBuffer, (uint)readBuffer.Length, out bytesRead, nativeOverlapped);
ThreadLogger.Log("After read in main thread");
Console.ReadLine();
}
}
FileReadAsyncResult.cs
class FileReadAsyncResult : IAsyncResult
{
public bool IsCompleted { get; private set; }
public WaitHandle AsyncWaitHandle { get; private set; }
public object AsyncState { get; private set; }
public bool CompletedSynchronously { get; private set; }
public Action<uint, byte[]> ReadCallback { get; set; }
public byte[] Buffer { get; set; }
}
IOCompletionWorker.cs
public class IOCompletionWorker
{
public unsafe void Start(IntPtr completionPort)
{
while (true)
{
uint bytesRead;
uint completionKey;
NativeOverlapped* nativeOverlapped;
ThreadLogger.Log("About to get queued completion status on {0}", completionPort);
var result = Interop.GetQueuedCompletionStatus(
completionPort,
out bytesRead,
out completionKey,
&nativeOverlapped,
uint.MaxValue);
var overlapped = Overlapped.Unpack(nativeOverlapped);
if (result)
{
var asyncResult = ((FileReadAsyncResult)overlapped.AsyncResult);
asyncResult.ReadCallback(bytesRead, asyncResult.Buffer);
}
else
{
ThreadLogger.Log(Interop.GetLastError().ToString());
}
Overlapped.Free(nativeOverlapped);
}
}
}
I'm aware that if I use Threadpool.BindHandle with the corresponding file handle, my callback will be ran - but I'm trying to learn why it's not being executed when it is being registered on my own IOCP which a thread is waiting for completion packages. (Furthermore, the Thread Pool won't know how to handle my custom AsyncResult - the callback there won't get executed.)