Vanara/System/Diagnostics/IoCompletionPort.cs

184 lines
7.7 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using Vanara.PInvoke;
using static Vanara.PInvoke.Kernel32;
namespace Vanara.Diagnostics
{
/// <summary>Represents a system I/O completion port.</summary>
/// <remarks>To use this class, create an instance with the <see cref="Create"/> method and then add one or more handlers.</remarks>
/// <seealso cref="System.IDisposable"/>
public class IoCompletionPort : IDisposable
{
private readonly ConcurrentDictionary<UIntPtr, Action<uint, UIntPtr, IntPtr>> handlers = new ConcurrentDictionary<UIntPtr, Action<uint, UIntPtr, IntPtr>>();
private bool disposedValue = false;
private HANDLE hComplPort;
private IoCompletionPort(HANDLE hValidPort)
{
hComplPort = hValidPort;
Task.Factory.StartNew(PollCompletionPortThread);
}
/// <summary>Finalizes an instance of the <see cref="IoCompletionPort"/> class.</summary>
~IoCompletionPort() => Dispose(false);
/// <summary>Gets the handle for the I/O completion port.</summary>
/// <value>The handle.</value>
public IntPtr Handle => (IntPtr)hComplPort;
/// <summary>
/// Creates an input/output (I/O) completion port that is not yet associated with a file handle, allowing association at a later time.
/// </summary>
/// <returns>An <see cref="IoCompletionPort"/> instance.</returns>
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);
}
/// <summary>Adds key and handler to the I/O completion port.</summary>
/// <param name="key">A unique completion key to be passed to the handler when called.</param>
/// <param name="handler">An action to perform when an I/O operation is complete.</param>
/// <exception cref="ArgumentOutOfRangeException">The value for <paramref name="key"/> cannot be <c>UIntPtr.Zero</c>.</exception>
/// <exception cref="InvalidOperationException">Key already exists.</exception>
public void AddKeyHandler(UIntPtr key, Action<uint, UIntPtr, IntPtr> 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.");
}
/// <summary>Adds an overlapped handle, key and handler to the I/O completion port.</summary>
/// <param name="overlappedHandle">
/// An open handle to an object that supports overlapped I/O.
/// <para>
/// 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.
/// </para>
/// </param>
/// <param name="key">A unique completion key to be passed to the handler when called.</param>
/// <param name="handler">An action to perform when an I/O operation is complete.</param>
/// <exception cref="ArgumentOutOfRangeException">The value for <paramref name="key"/> cannot be <c>UIntPtr.Zero</c>.</exception>
/// <exception cref="InvalidOperationException">Key already exists.</exception>
public void AddKeyHandler(IntPtr overlappedHandle, UIntPtr key, Action<uint, UIntPtr, IntPtr> 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.
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>Posts an I/O completion packet to an I/O completion port.</summary>
/// <param name="completionKey">
/// The value to be returned through the lpCompletionKey parameter of the <c>GetQueuedCompletionStatus</c> function.
/// </param>
/// <param name="numberOfBytesTransferred">
/// The value to be returned through the lpNumberOfBytesTransferred parameter of the <c>GetQueuedCompletionStatus</c> function.
/// </param>
/// <param name="lpOverlapped">
/// The value to be returned through the lpOverlapped parameter of the <c>GetQueuedCompletionStatus</c> function.
/// </param>
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();
}
/// <summary>Posts an I/O completion packet to an I/O completion port.</summary>
/// <param name="completionKey">
/// The value to be returned through the lpCompletionKey parameter of the <c>GetQueuedCompletionStatus</c> function.
/// </param>
/// <param name="numberOfBytesTransferred">
/// The value to be returned through the lpNumberOfBytesTransferred parameter of the <c>GetQueuedCompletionStatus</c> function.
/// </param>
/// <param name="lpOverlapped">
/// The value to be returned through the lpOverlapped parameter of the <c>GetQueuedCompletionStatus</c> function.
/// </param>
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();
}
/// <summary>Removes the handler associated with <paramref name="key"/>.</summary>
/// <param name="key">The key of the handler to remove.</param>
/// <exception cref="InvalidOperationException">Key does not exist.</exception>
public void RemoveKeyHandler(UIntPtr key)
{
if (!handlers.TryRemove(key, out _))
throw new InvalidOperationException("Key does not exist.");
}
/// <summary>Releases unmanaged and - optionally - managed resources.</summary>
/// <param name="disposing">
/// <see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.
/// </param>
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<uint, UIntPtr, IntPtr> t) action(t.Item1, t.Item2, t.Item3); }, new Tuple<uint, UIntPtr, IntPtr>(byteCount, completionKey, overlapped));
}
}
}
}