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 IntPtr.Zero.
/// Key already exists.
public void AddKeyHandler(IntPtr key, Action handler)
{
if (key == IntPtr.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 IntPtr.Zero.
/// Key already exists.
public void AddKeyHandler(IntPtr overlappedHandle, IntPtr 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(IntPtr completionKey, uint numberOfBytesTransferred = 0, IntPtr lpOverlapped = default)
{
if (completionKey == IntPtr.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(IntPtr completionKey, uint numberOfBytesTransferred, NativeOverlapped* lpOverlapped)
{
if (completionKey == IntPtr.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(IntPtr 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 == IntPtr.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));
}
}
}
}