using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; using Vanara.PInvoke; using static Vanara.PInvoke.Kernel32; namespace Vanara.Diagnostics { /// Represents a system I/O completion port. /// To use this class, create an instance with the method and then add one or more handlers. /// public class IoCompletionPort : IDisposable { private readonly ConcurrentDictionary> handlers = new ConcurrentDictionary>(); private bool disposedValue = false; private HANDLE hComplPort; private IoCompletionPort(HANDLE hValidPort) { hComplPort = hValidPort; Task.Factory.StartNew(PollCompletionPortThread); } /// Finalizes an instance of the class. ~IoCompletionPort() => Dispose(false); /// Gets the handle for the I/O completion port. /// The handle. public IntPtr Handle => (IntPtr)hComplPort; /// /// Creates an input/output (I/O) completion port that is not yet associated with a file handle, allowing association at a later time. /// /// An instance. public static IoCompletionPort Create() { var hComplPort = CreateIoCompletionPort((IntPtr)HFILE.INVALID_HANDLE_VALUE, HANDLE.NULL, default, 0); if (hComplPort.IsNull) Win32Error.ThrowLastError(); return new IoCompletionPort(hComplPort); } /// Adds key and handler to the I/O completion port. /// A unique completion key to be passed to the handler when called. /// An action to perform when an I/O operation is complete. /// The value for cannot be UIntPtr.Zero. /// Key already exists. public void AddKeyHandler(UIntPtr key, Action handler) { if (key == UIntPtr.Zero) throw new ArgumentOutOfRangeException(nameof(key), "Key value cannot be 0."); if (!handlers.TryAdd(key, handler)) throw new InvalidOperationException("Key already exists."); } /// Adds an overlapped handle, key and handler to the I/O completion port. /// /// An open handle to an object that supports overlapped I/O. /// /// The provided handle has to have been opened for overlapped I/O completion. For example, you must specify the FILE_FLAG_OVERLAPPED /// flag when using the CreateFile function to obtain the handle. /// /// /// A unique completion key to be passed to the handler when called. /// An action to perform when an I/O operation is complete. /// The value for cannot be UIntPtr.Zero. /// Key already exists. public void AddKeyHandler(IntPtr overlappedHandle, UIntPtr key, Action handler) { AddKeyHandler(key, handler); if (CreateIoCompletionPort(overlappedHandle, hComplPort, key, 0).IsNull) Win32Error.ThrowLastError(); } // Do not change this code. Put cleanup code in Dispose(bool disposing) above. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// Posts an I/O completion packet to an I/O completion port. /// /// The value to be returned through the lpCompletionKey parameter of the GetQueuedCompletionStatus function. /// /// /// The value to be returned through the lpNumberOfBytesTransferred parameter of the GetQueuedCompletionStatus function. /// /// /// The value to be returned through the lpOverlapped parameter of the GetQueuedCompletionStatus function. /// public void PostQueuedStatus(UIntPtr completionKey, uint numberOfBytesTransferred = 0, IntPtr lpOverlapped = default) { if (completionKey == UIntPtr.Zero) throw new ArgumentOutOfRangeException(nameof(completionKey), "Key value cannot be 0."); if (!PostQueuedCompletionStatus(hComplPort, numberOfBytesTransferred, completionKey, lpOverlapped)) Win32Error.ThrowLastError(); } /// Posts an I/O completion packet to an I/O completion port. /// /// The value to be returned through the lpCompletionKey parameter of the GetQueuedCompletionStatus function. /// /// /// The value to be returned through the lpNumberOfBytesTransferred parameter of the GetQueuedCompletionStatus function. /// /// /// The value to be returned through the lpOverlapped parameter of the GetQueuedCompletionStatus function. /// public unsafe void PostQueuedStatus(UIntPtr completionKey, uint numberOfBytesTransferred, NativeOverlapped* lpOverlapped) { if (completionKey == UIntPtr.Zero) throw new ArgumentOutOfRangeException(nameof(completionKey), "Key value cannot be 0."); if (!PostQueuedCompletionStatus(hComplPort, numberOfBytesTransferred, completionKey, lpOverlapped)) Win32Error.ThrowLastError(); } /// Removes the handler associated with . /// The key of the handler to remove. /// Key does not exist. public void RemoveKeyHandler(UIntPtr key) { if (!handlers.TryRemove(key, out _)) throw new InvalidOperationException("Key does not exist."); } /// Releases unmanaged and - optionally - managed resources. /// /// to release both managed and unmanaged resources; to release only unmanaged resources. /// protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { // TODO: dispose managed state (managed objects). } if (!hComplPort.IsNull) { // Shut down background thread processing completion port messages. PostQueuedCompletionStatus(hComplPort, 0); // Close the completion port handle CloseHandle((IntPtr)hComplPort); hComplPort = HANDLE.NULL; } disposedValue = true; } } private void PollCompletionPortThread() { while (true) { // Wait forever to get the next completion status if (!GetQueuedCompletionStatus(hComplPort, out var byteCount, out var completionKey, out var overlapped, INFINITE) && overlapped == IntPtr.Zero) { var err = Win32Error.GetLastError(); if (err == Win32Error.ERROR_ABANDONED_WAIT_0) break; throw err.GetException(); } // End the thread if terminating completion key signals if (byteCount == 0 && completionKey == UIntPtr.Zero && overlapped == IntPtr.Zero) break; // Spin this off so we don't hang the completion port. if (handlers.TryGetValue(completionKey, out var action)) Task.Factory.StartNew(o => { if (o is Tuple t) action(t.Item1, t.Item2, t.Item3); }, new Tuple(byteCount, completionKey, overlapped)); } } } }