When running on Windows, enable an option to switch between the Portable thread pool and the Windows thread pool.
This change targets NativeAOT, CoreCLR, and Mono.
Windows threadpool remains as the default for NativeAOT on Windows. For any other case the Portable thread pool remains the default.
Tests with the Windows thread pool enabled were added to the System.Threading.Threadpool solution.
Trimming is enabled to remove the unused thread pool.
---------
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Co-authored-by: Koundinya Veluri <kouvel@users.noreply.github.com>
<Compile Include="$(CommonPath)Interop\Windows\OleAut32\Interop.VariantClear.cs">
<Link>Common\Interop\Windows\OleAut32\Interop.VariantClear.cs</Link>
</Compile>
- <Compile Include="$(BclSourcesRoot)\System\Threading\ThreadPool.CoreCLR.Windows.cs" />
</ItemGroup>
<ItemGroup Condition="'$(FeatureObjCMarshal)' == 'true'">
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ObjectiveCMarshal.CoreCLR.cs" />
#else // FEATURE_COMINTEROP_APARTMENT_SUPPORT
private static bool SetApartmentStateUnchecked(ApartmentState state, bool throwOnError)
{
- if (state != ApartmentState.Unknown)
- {
+ if (state != ApartmentState.Unknown)
+ {
if (throwOnError)
{
throw new PlatformNotSupportedException(SR.PlatformNotSupported_ComInterop);
}
return false;
- }
+ }
- return true;
+ return true;
}
#endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Runtime.Versioning;
-
-namespace System.Threading
-{
- public static partial class ThreadPool
- {
- [CLSCompliant(false)]
- [SupportedOSPlatform("windows")]
- public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped)
- {
- if (overlapped == null)
- {
- ThrowHelper.ThrowArgumentNullException(ExceptionArgument.overlapped);
- }
-
- // OS doesn't signal handle, so do it here
- overlapped->InternalLow = IntPtr.Zero;
-
- PortableThreadPool.ThreadPoolInstance.QueueNativeOverlapped(overlapped);
- return true;
- }
-
- [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")]
- [SupportedOSPlatform("windows")]
- public static bool BindHandle(IntPtr osHandle)
- {
- PortableThreadPool.ThreadPoolInstance.RegisterForIOCompletionNotifications(osHandle);
- return true;
- }
-
- [SupportedOSPlatform("windows")]
- public static bool BindHandle(SafeHandle osHandle)
- {
- ArgumentNullException.ThrowIfNull(osHandle);
-
- bool mustReleaseSafeHandle = false;
- try
- {
- osHandle.DangerousAddRef(ref mustReleaseSafeHandle);
-
- PortableThreadPool.ThreadPoolInstance.RegisterForIOCompletionNotifications(osHandle.DangerousGetHandle());
- return true;
- }
- finally
- {
- if (mustReleaseSafeHandle)
- osHandle.DangerousRelease();
- }
- }
- }
-}
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
/*=============================================================================
namespace System.Threading
{
- internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkItem
- {
- void IThreadPoolWorkItem.Execute() => CompleteWait();
-
- // Entry point from unmanaged code
- private void CompleteWait()
- {
- PortableThreadPool.CompleteWait(_registeredWaitHandle, _timedOut);
- }
- }
-
- internal sealed class UnmanagedThreadPoolWorkItem : IThreadPoolWorkItem
- {
- private readonly IntPtr _callback;
- private readonly IntPtr _state;
-
- public UnmanagedThreadPoolWorkItem(IntPtr callback, IntPtr state)
- {
- _callback = callback;
- _state = state;
- }
-
- unsafe void IThreadPoolWorkItem.Execute() => ((delegate* unmanaged<IntPtr, int>)_callback)(_state);
- }
public static partial class ThreadPool
{
private static readonly bool s_initialized = InitializeConfig();
- // Indicates whether the thread pool should yield the thread from the dispatch loop to the runtime periodically so that
- // the runtime may use the thread for processing other work
- internal static bool YieldFromDispatchLoop => false;
-
- private static readonly bool IsWorkerTrackingEnabledInConfig = GetEnableWorkerTracking();
-
private static unsafe bool InitializeConfig()
{
int configVariableIndex = 1;
out bool isBoolean,
out char* appContextConfigName);
- private static bool GetEnableWorkerTracking() =>
- AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.EnableWorkerTracking", false);
-
- public static bool SetMaxThreads(int workerThreads, int completionPortThreads)
- {
- return PortableThreadPool.ThreadPoolInstance.SetMaxThreads(workerThreads, completionPortThreads);
- }
-
- public static void GetMaxThreads(out int workerThreads, out int completionPortThreads)
- {
- PortableThreadPool.ThreadPoolInstance.GetMaxThreads(out workerThreads, out completionPortThreads);
- }
-
- public static bool SetMinThreads(int workerThreads, int completionPortThreads)
- {
- return PortableThreadPool.ThreadPoolInstance.SetMinThreads(workerThreads, completionPortThreads);
- }
-
- public static void GetMinThreads(out int workerThreads, out int completionPortThreads)
- {
- PortableThreadPool.ThreadPoolInstance.GetMinThreads(out workerThreads, out completionPortThreads);
- }
-
- public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads)
- {
- PortableThreadPool.ThreadPoolInstance.GetAvailableThreads(out workerThreads, out completionPortThreads);
- }
-
- /// <summary>
- /// Gets the number of thread pool threads that currently exist.
- /// </summary>
- /// <remarks>
- /// For a thread pool implementation that may have different types of threads, the count includes all types.
- /// </remarks>
- public static int ThreadCount
- {
- get
- {
- return PortableThreadPool.ThreadPoolInstance.ThreadCount;
- }
- }
-
- /// <summary>
- /// Gets the number of work items that have been processed so far.
- /// </summary>
- /// <remarks>
- /// For a thread pool implementation that may have different types of work items, the count includes all types.
- /// </remarks>
- public static long CompletedWorkItemCount
- {
- get
- {
- return PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount;
- }
- }
-
- private static RegisteredWaitHandle RegisterWaitForSingleObject(
- WaitHandle waitObject,
- WaitOrTimerCallback callBack,
- object? state,
- uint millisecondsTimeOutInterval,
- bool executeOnlyOnce,
- bool flowExecutionContext)
- {
- ArgumentNullException.ThrowIfNull(waitObject);
- ArgumentNullException.ThrowIfNull(callBack);
-
- RegisteredWaitHandle registeredWaitHandle = new RegisteredWaitHandle(
- waitObject,
- new _ThreadPoolWaitOrTimerCallback(callBack, state, flowExecutionContext),
- (int)millisecondsTimeOutInterval,
- !executeOnlyOnce);
-
- PortableThreadPool.ThreadPoolInstance.RegisterWaitHandle(registeredWaitHandle);
-
- return registeredWaitHandle;
- }
-
- internal static void RequestWorkerThread()
- {
- PortableThreadPool.ThreadPoolInstance.RequestWorker();
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject, int currentTimeMs)
- {
- return
- PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete(
- threadLocalCompletionCountObject,
- currentTimeMs);
- }
-
- internal static void ReportThreadStatus(bool isWorking)
- {
- PortableThreadPool.ThreadPoolInstance.ReportThreadStatus(isWorking);
- }
-
- internal static void NotifyWorkItemProgress()
- {
- PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress();
- }
-
- internal static bool NotifyThreadBlocked() => PortableThreadPool.ThreadPoolInstance.NotifyThreadBlocked();
-
- internal static void NotifyThreadUnblocked()
- {
- PortableThreadPool.ThreadPoolInstance.NotifyThreadUnblocked();
- }
-
- internal static object? GetOrCreateThreadLocalCompletionCountObject() =>
- PortableThreadPool.ThreadPoolInstance.GetOrCreateThreadLocalCompletionCountObject();
}
}
<UseSystemResourceKeys Condition="$(IlcDisableReflection) == 'true'">true</UseSystemResourceKeys>
<EventSourceSupport Condition="$(IlcDisableReflection) == 'true'">false</EventSourceSupport>
<EventSourceSupport Condition="$(EventSourceSupport) == ''">false</EventSourceSupport>
+ <UseWindowsThreadPool Condition="'$(UseWindowsThreadPool)' == '' and '$(_targetOS)' == 'win'">true</UseWindowsThreadPool>
<DynamicCodeSupport Condition="'$(DynamicCodeSupport)' == ''">false</DynamicCodeSupport>
</PropertyGroup>
<!-- The managed debugging support in libraries is unused - trim it -->
<IlcArg Condition="'$(IlcKeepManagedDebuggerSupport)' != 'true'" Include="--feature:System.Diagnostics.Debugger.IsSupported=false" />
+ <IlcArg Condition="'$(UseWindowsThreadPool)' != '' and '$(_targetOS)' == 'win'" Include="--feature:System.Threading.ThreadPool.UseWindowsThreadPool=$(UseWindowsThreadPool)" />
</ItemGroup>
<MakeDir Directories="$(NativeIntermediateOutputPath)" />
</PropertyGroup>
<PropertyGroup>
- <FeaturePortableThreadPool Condition="'$(FeaturePortableThreadPool)' == ''">false</FeaturePortableThreadPool>
- <FeaturePortableThreadPool Condition="'$(TargetsUnix)' == 'true'">true</FeaturePortableThreadPool>
+ <FeaturePortableThreadPool>true</FeaturePortableThreadPool>
</PropertyGroup>
<PropertyGroup>
- <FeaturePortableTimer Condition="'$(FeaturePortableTimer)' == ''">false</FeaturePortableTimer>
- <FeaturePortableTimer Condition="'$(TargetsUnix)' == 'true'">true</FeaturePortableTimer>
+ <FeaturePortableTimer>true</FeaturePortableTimer>
</PropertyGroup>
<PropertyGroup>
<FeatureHardwareIntrinsics>true</FeatureHardwareIntrinsics>
<Compile Include="System\Threading\ObjectHeader.cs" />
<Compile Include="System\Threading\SyncTable.cs" />
<Compile Include="System\Threading\Thread.NativeAot.cs" />
- <Compile Include="System\Threading\ThreadPool.NativeAot.cs" />
<Compile Include="System\Type.NativeAot.cs" />
<Compile Include="System\Type.Internal.cs" />
<Compile Include="System\TypedReference.cs" />
</Compile>
</ItemGroup>
<ItemGroup Condition="'$(TargetsWindows)'=='true'">
- <Compile Include="Microsoft\Win32\SafeHandles\SafeThreadPoolIOHandle.cs" />
<Compile Include="System\Runtime\InteropServices\NativeLibrary.NativeAot.Windows.cs" />
<Compile Include="System\Runtime\InteropServices\PInvokeMarshal.Windows.cs" />
<Compile Include="$(CommonPath)\System\Runtime\InteropServices\Variant.cs">
<Compile Include="$(CommonPath)\Interop\Windows\Kernel32\Interop.GetTickCount64.cs">
<Link>Interop\Windows\Kernel32\Interop.GetTickCount64.cs</Link>
</Compile>
- <Compile Include="$(CommonPath)\Interop\Windows\Kernel32\Interop.ThreadPool.cs">
- <Link>Interop\Windows\Kernel32\Interop.ThreadPool.cs</Link>
- </Compile>
- <Compile Include="System\Threading\Timer.Windows.cs" Condition="'$(FeaturePortableTimer)' != 'true'" />
- <Compile Include="$(CommonPath)\Interop\Windows\Kernel32\Interop.Timer.cs">
- <Link>Interop\Windows\Kernel32\Interop.Timer.cs</Link>
- </Compile>
<Compile Include="$(CommonPath)\Interop\Windows\Kernel32\Interop.DynamicLoad.cs">
<Link>Interop\Windows\Kernel32\Interop.DynamicLoad.cs</Link>
</Compile>
<Compile Include="System\Threading\Thread.NativeAot.Windows.cs" />
- <Compile Include="$(CommonPath)\Interop\Windows\Kernel32\Interop.ThreadPoolIO.cs">
- <Link>Interop\Windows\Kernel32\Interop.ThreadPoolIO.cs</Link>
- </Compile>
</ItemGroup>
- <ItemGroup Condition="'$(TargetsWindows)'=='true' and '$(FeaturePortableThreadPool)' != 'true'">
- <Compile Include="System\Threading\ThreadPool.Windows.cs" />
- <Compile Include="System\Threading\ThreadPoolCallbackWrapper.cs" />
- <Compile Include="System\Threading\Win32ThreadPoolBoundHandle.cs" />
- <Compile Include="System\Threading\Win32ThreadPoolNativeOverlapped.cs" />
- <Compile Include="System\Threading\Win32ThreadPoolNativeOverlapped.ExecutionContextCallbackArgs.cs" />
- <Compile Include="System\Threading\Win32ThreadPoolNativeOverlapped.OverlappedData.cs" />
- <Compile Include="System\Threading\Win32ThreadPoolPreAllocatedOverlapped.cs" />
+ <ItemGroup Condition="'$(TargetsWindows)'=='true'">
+ <Compile Include="System\Threading\ThreadPoolCallbackWrapper.NativeAot.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsUnix)'=='true'">
<Compile Include="System\Environment.NativeAot.Unix.cs" />
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-/*=============================================================================
-**
-**
-**
-** Purpose: Class for creating and managing a threadpool
-**
-**
-=============================================================================*/
-
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-using System.Runtime.Versioning;
-
-namespace System.Threading
-{
- public static partial class ThreadPool
- {
- [Conditional("unnecessary")]
- internal static void ReportThreadStatus(bool isWorking)
- {
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.Win32.SafeHandles;
-using System.Diagnostics;
-using System.Runtime;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Runtime.Versioning;
-
-namespace System.Threading
-{
- //
- // Windows-specific implementation of ThreadPool
- //
-#if !FEATURE_WASM_THREADS
- [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
-#endif
- public sealed class RegisteredWaitHandle : MarshalByRefObject
- {
- private readonly Lock _lock;
- private SafeWaitHandle _waitHandle;
- private readonly _ThreadPoolWaitOrTimerCallback _callbackHelper;
- private readonly uint _millisecondsTimeout;
- private bool _repeating;
- private bool _unregistering;
-
- // Handle to this object to keep it alive
- private GCHandle _gcHandle;
-
- // Pointer to the TP_WAIT structure
- private IntPtr _tpWait;
-
- internal unsafe RegisteredWaitHandle(SafeWaitHandle waitHandle, _ThreadPoolWaitOrTimerCallback callbackHelper,
- uint millisecondsTimeout, bool repeating)
- {
- _lock = new Lock();
-
- // Protect the handle from closing while we are waiting on it (VSWhidbey 285642)
- waitHandle.DangerousAddRef();
- _waitHandle = waitHandle;
-
- _callbackHelper = callbackHelper;
- _millisecondsTimeout = millisecondsTimeout;
- _repeating = repeating;
-
- // Allocate _gcHandle and _tpWait as the last step and make sure they are never leaked
- _gcHandle = GCHandle.Alloc(this);
-
- _tpWait = Interop.Kernel32.CreateThreadpoolWait(&RegisteredWaitCallback, (IntPtr)_gcHandle, IntPtr.Zero);
-
- if (_tpWait == IntPtr.Zero)
- {
- _gcHandle.Free();
- throw new OutOfMemoryException();
- }
- }
-
- [UnmanagedCallersOnly]
- internal static void RegisteredWaitCallback(IntPtr instance, IntPtr context, IntPtr wait, uint waitResult)
- {
- var wrapper = ThreadPoolCallbackWrapper.Enter();
- GCHandle handle = (GCHandle)context;
- RegisteredWaitHandle registeredWaitHandle = (RegisteredWaitHandle)handle.Target!;
- Debug.Assert((handle == registeredWaitHandle._gcHandle) && (wait == registeredWaitHandle._tpWait));
-
- bool timedOut = (waitResult == (uint)Interop.Kernel32.WAIT_TIMEOUT);
- registeredWaitHandle.PerformCallback(timedOut);
- ThreadPool.IncrementCompletedWorkItemCount();
- wrapper.Exit();
- }
-
- private void PerformCallback(bool timedOut)
- {
- bool lockAcquired;
- var spinner = new SpinWait();
-
- // Prevent the race condition with Unregister and the previous PerformCallback call, which may still be
- // holding the _lock.
- while (!(lockAcquired = _lock.TryAcquire(0)) && !Volatile.Read(ref _unregistering))
- {
- spinner.SpinOnce();
- }
-
- // If another thread is running Unregister, no need to restart the timer or clean up
- if (lockAcquired)
- {
- try
- {
- if (!_unregistering)
- {
- if (_repeating)
- {
- // Allow this wait to fire again. Restart the timer before executing the callback.
- RestartWait();
- }
- else
- {
- // This wait will not be fired again. Free the GC handle to allow the GC to collect this object.
- Debug.Assert(_gcHandle.IsAllocated);
- _gcHandle.Free();
- }
- }
- }
- finally
- {
- _lock.Release();
- }
- }
-
- _ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(_callbackHelper, timedOut);
- }
-
- internal unsafe void RestartWait()
- {
- long timeout;
- long* pTimeout = null; // Null indicates infinite timeout
-
- if (_millisecondsTimeout != Timeout.UnsignedInfinite)
- {
- timeout = -10000L * _millisecondsTimeout;
- pTimeout = &timeout;
- }
-
- // We can use DangerousGetHandle because of DangerousAddRef in the constructor
- Interop.Kernel32.SetThreadpoolWait(_tpWait, _waitHandle.DangerousGetHandle(), (IntPtr)pTimeout);
- }
-
- public bool Unregister(WaitHandle waitObject)
- {
- // Hold the lock during the synchronous part of Unregister (as in CoreCLR)
- using (LockHolder.Hold(_lock))
- {
- if (!_unregistering)
- {
- // Ensure callbacks will not call SetThreadpoolWait anymore
- _unregistering = true;
-
- // Cease queueing more callbacks
- Interop.Kernel32.SetThreadpoolWait(_tpWait, IntPtr.Zero, IntPtr.Zero);
-
- // Should we wait for callbacks synchronously? Note that we treat the zero handle as the asynchronous case.
- SafeWaitHandle? safeWaitHandle = waitObject?.SafeWaitHandle;
- bool blocking = ((safeWaitHandle != null) && (safeWaitHandle.DangerousGetHandle() == new IntPtr(-1)));
-
- if (blocking)
- {
- FinishUnregistering();
- }
- else
- {
- // Wait for callbacks and dispose resources asynchronously
- ThreadPool.QueueUserWorkItem(FinishUnregisteringAsync, safeWaitHandle);
- }
-
- return true;
- }
- }
- return false;
- }
-
- private void FinishUnregistering()
- {
- Debug.Assert(_unregistering);
-
- // Wait for outstanding wait callbacks to complete
- Interop.Kernel32.WaitForThreadpoolWaitCallbacks(_tpWait, false);
-
- // Now it is safe to dispose resources
- Interop.Kernel32.CloseThreadpoolWait(_tpWait);
- _tpWait = IntPtr.Zero;
-
- if (_gcHandle.IsAllocated)
- {
- _gcHandle.Free();
- }
-
- Debug.Assert(_waitHandle != null);
- _waitHandle.DangerousRelease();
- _waitHandle = null;
-
- GC.SuppressFinalize(this);
- }
-
- private void FinishUnregisteringAsync(object? waitObject)
- {
- FinishUnregistering();
-
- // Signal the provided wait object
- SafeWaitHandle? safeWaitHandle = (SafeWaitHandle?)waitObject;
-
- if ((safeWaitHandle != null) && !safeWaitHandle.IsInvalid)
- {
- Interop.Kernel32.SetEvent(safeWaitHandle);
- }
- }
-
- ~RegisteredWaitHandle()
- {
- // If _gcHandle is allocated, it points to this object, so this object must not be collected by the GC
- Debug.Assert(!_gcHandle.IsAllocated);
-
- // If this object gets resurrected and another thread calls Unregister, that creates a race condition.
- // Do not block the finalizer thread. If another thread is running Unregister, it will clean up for us.
- // The _lock may be null in case of OOM in the constructor.
- if ((_lock != null) && _lock.TryAcquire(0))
- {
- try
- {
- if (!_unregistering)
- {
- _unregistering = true;
-
- if (_tpWait != IntPtr.Zero)
- {
- // There must be no in-flight callbacks; just dispose resources
- Interop.Kernel32.CloseThreadpoolWait(_tpWait);
- _tpWait = IntPtr.Zero;
- }
-
- if (_waitHandle != null)
- {
- _waitHandle.DangerousRelease();
- _waitHandle = null;
- }
- }
- }
- finally
- {
- _lock.Release();
- }
- }
- }
- }
-
- public static partial class ThreadPool
- {
- internal const bool IsWorkerTrackingEnabledInConfig = false;
-
- // Indicates whether the thread pool should yield the thread from the dispatch loop to the runtime periodically so that
- // the runtime may use the thread for processing other work.
- //
- // Windows thread pool threads need to yield back to the thread pool periodically, otherwise those threads may be
- // considered to be doing long-running work and change thread pool heuristics, such as slowing or halting thread
- // injection.
- internal static bool YieldFromDispatchLoop => true;
-
- /// <summary>
- /// The maximum number of threads in the default thread pool on Windows 10 as computed by
- /// TppComputeDefaultMaxThreads(TppMaxGlobalPool).
- /// </summary>
- /// <remarks>
- /// Note that Windows 8 and 8.1 used a different value: Math.Max(4 * Environment.ProcessorCount, 512).
- /// </remarks>
- private static readonly int MaxThreadCount = Math.Max(8 * Environment.ProcessorCount, 768);
-
- private static IntPtr s_work;
-
- private class ThreadCountHolder
- {
- internal ThreadCountHolder() => Interlocked.Increment(ref s_threadCount);
- ~ThreadCountHolder() => Interlocked.Decrement(ref s_threadCount);
- }
-
- [ThreadStatic]
- private static ThreadCountHolder t_threadCountHolder;
- private static int s_threadCount;
-
- [StructLayout(LayoutKind.Sequential)]
- private struct WorkingThreadCounter
- {
- private readonly Internal.PaddingFor32 pad1;
-
- public volatile int Count;
-
- private readonly Internal.PaddingFor32 pad2;
- }
-
- // The number of threads executing work items in the Dispatch method
- private static WorkingThreadCounter s_workingThreadCounter;
-
- private static readonly ThreadInt64PersistentCounter s_completedWorkItemCounter = new ThreadInt64PersistentCounter();
-
- [ThreadStatic]
- private static object? t_completionCountObject;
-
- internal static void InitializeForThreadPoolThread() => t_threadCountHolder = new ThreadCountHolder();
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static void IncrementCompletedWorkItemCount() => ThreadInt64PersistentCounter.Increment(GetOrCreateThreadLocalCompletionCountObject());
-
- internal static object GetOrCreateThreadLocalCompletionCountObject() =>
- t_completionCountObject ?? CreateThreadLocalCompletionCountObject();
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static object CreateThreadLocalCompletionCountObject()
- {
- Debug.Assert(t_completionCountObject == null);
-
- object threadLocalCompletionCountObject = s_completedWorkItemCounter.CreateThreadLocalCountObject();
- t_completionCountObject = threadLocalCompletionCountObject;
- return threadLocalCompletionCountObject;
- }
-
- public static bool SetMaxThreads(int workerThreads, int completionPortThreads)
- {
- // Not supported at present
- return false;
- }
-
- public static void GetMaxThreads(out int workerThreads, out int completionPortThreads)
- {
- // Note that worker threads and completion port threads share the same thread pool.
- // The total number of threads cannot exceed MaxThreadCount.
- workerThreads = MaxThreadCount;
- completionPortThreads = MaxThreadCount;
- }
-
- public static bool SetMinThreads(int workerThreads, int completionPortThreads)
- {
- // Not supported at present
- return false;
- }
-
- public static void GetMinThreads(out int workerThreads, out int completionPortThreads)
- {
- workerThreads = 0;
- completionPortThreads = 0;
- }
-
- public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads)
- {
- // Make sure we return a non-negative value if thread pool defaults are changed
- int availableThreads = Math.Max(MaxThreadCount - s_workingThreadCounter.Count, 0);
-
- workerThreads = availableThreads;
- completionPortThreads = availableThreads;
- }
-
- /// <summary>
- /// Gets the number of thread pool threads that currently exist.
- /// </summary>
- /// <remarks>
- /// For a thread pool implementation that may have different types of threads, the count includes all types.
- /// </remarks>
- public static int ThreadCount => s_threadCount;
-
- /// <summary>
- /// Gets the number of work items that have been processed so far.
- /// </summary>
- /// <remarks>
- /// For a thread pool implementation that may have different types of work items, the count includes all types.
- /// </remarks>
- public static long CompletedWorkItemCount => s_completedWorkItemCounter.Count;
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static void NotifyWorkItemProgress() => IncrementCompletedWorkItemCount();
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject, int _ /*currentTimeMs*/)
- {
- ThreadInt64PersistentCounter.Increment(threadLocalCompletionCountObject);
- return true;
- }
-
- internal static bool NotifyThreadBlocked() { return false; }
- internal static void NotifyThreadUnblocked() { }
-
- [UnmanagedCallersOnly]
- private static void DispatchCallback(IntPtr instance, IntPtr context, IntPtr work)
- {
- var wrapper = ThreadPoolCallbackWrapper.Enter();
- Debug.Assert(s_work == work);
- Interlocked.Increment(ref s_workingThreadCounter.Count);
- ThreadPoolWorkQueue.Dispatch();
- Interlocked.Decrement(ref s_workingThreadCounter.Count);
- // We reset the thread after executing each callback
- wrapper.Exit(resetThread: false);
- }
-
- internal static unsafe void RequestWorkerThread()
- {
- if (s_work == IntPtr.Zero)
- {
- IntPtr work = Interop.Kernel32.CreateThreadpoolWork(&DispatchCallback, IntPtr.Zero, IntPtr.Zero);
- if (work == IntPtr.Zero)
- throw new OutOfMemoryException();
-
- if (Interlocked.CompareExchange(ref s_work, work, IntPtr.Zero) != IntPtr.Zero)
- Interop.Kernel32.CloseThreadpoolWork(work);
- }
-
- Interop.Kernel32.SubmitThreadpoolWork(s_work);
- }
-
- private static RegisteredWaitHandle RegisterWaitForSingleObject(
- WaitHandle waitObject,
- WaitOrTimerCallback callBack,
- object state,
- uint millisecondsTimeOutInterval,
- bool executeOnlyOnce,
- bool flowExecutionContext)
- {
- ArgumentNullException.ThrowIfNull(waitObject);
- ArgumentNullException.ThrowIfNull(callBack);
-
- var callbackHelper = new _ThreadPoolWaitOrTimerCallback(callBack, state, flowExecutionContext);
- var registeredWaitHandle = new RegisteredWaitHandle(waitObject.SafeWaitHandle, callbackHelper, millisecondsTimeOutInterval, !executeOnlyOnce);
-
- registeredWaitHandle.RestartWait();
- return registeredWaitHandle;
- }
-
- private static unsafe void NativeOverlappedCallback(nint overlappedPtr) =>
- IOCompletionCallbackHelper.PerformSingleIOCompletionCallback(0, 0, (NativeOverlapped*)overlappedPtr);
-
- [CLSCompliant(false)]
- [SupportedOSPlatform("windows")]
- public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped)
- {
- if (overlapped == null)
- {
- ThrowHelper.ThrowArgumentNullException(ExceptionArgument.overlapped);
- }
-
- // OS doesn't signal handle, so do it here
- overlapped->InternalLow = (IntPtr)0;
- // Both types of callbacks are executed on the same thread pool
- return UnsafeQueueUserWorkItem(NativeOverlappedCallback, (nint)overlapped, preferLocal: false);
- }
-
- [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")]
- [SupportedOSPlatform("windows")]
- public static bool BindHandle(IntPtr osHandle)
- {
- throw new PlatformNotSupportedException(SR.Arg_PlatformNotSupported); // Replaced by ThreadPoolBoundHandle.BindHandle
- }
-
- [SupportedOSPlatform("windows")]
- public static bool BindHandle(SafeHandle osHandle)
- {
- throw new PlatformNotSupportedException(SR.Arg_PlatformNotSupported); // Replaced by ThreadPoolBoundHandle.BindHandle
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Diagnostics;
-
-namespace System.Threading
-{
- public sealed class PreAllocatedOverlapped : IDisposable, IDeferredDisposable
- {
- internal readonly unsafe Win32ThreadPoolNativeOverlapped* _overlapped;
- private DeferredDisposableLifetime<PreAllocatedOverlapped> _lifetime;
-
- [CLSCompliant(false)]
- public PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData) :
- this(callback, state, pinData, flowExecutionContext: true)
- {
- }
-
- [CLSCompliant(false)]
- public static PreAllocatedOverlapped UnsafeCreate(IOCompletionCallback callback, object? state, object? pinData) =>
- new PreAllocatedOverlapped(callback, state, pinData, flowExecutionContext: false);
-
- private unsafe PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData, bool flowExecutionContext)
- {
- ArgumentNullException.ThrowIfNull(callback);
-
- _overlapped = Win32ThreadPoolNativeOverlapped.Allocate(callback, state, pinData, this, flowExecutionContext);
- }
-
- internal bool AddRef()
- {
- return _lifetime.AddRef();
- }
-
- internal void Release()
- {
- _lifetime.Release(this);
- }
-
- public void Dispose()
- {
- _lifetime.Dispose(this);
- GC.SuppressFinalize(this);
- }
-
- ~PreAllocatedOverlapped()
- {
- Dispose();
- }
-
- unsafe void IDeferredDisposable.OnFinalRelease(bool disposed)
- {
- if (_overlapped != null)
- {
- if (disposed)
- Win32ThreadPoolNativeOverlapped.Free(_overlapped);
- else
- *Win32ThreadPoolNativeOverlapped.ToNativeOverlapped(_overlapped) = default(NativeOverlapped);
- }
- }
-
- internal unsafe bool IsUserObject(byte[]? buffer) => _overlapped->IsUserObject(buffer);
- }
-}
case 2: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_ForceMaxWorkerThreads, false, W("System.Threading.ThreadPool.MaxThreads"))) { return 3; } FALLTHROUGH;
case 3: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_DisableStarvationDetection, true, W("System.Threading.ThreadPool.DisableStarvationDetection"))) { return 4; } FALLTHROUGH;
case 4: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_DebugBreakOnWorkerStarvation, true, W("System.Threading.ThreadPool.DebugBreakOnWorkerStarvation"))) { return 5; } FALLTHROUGH;
- case 5: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking, true, W("System.Threading.ThreadPool.EnableWorkerTracking"))) { return 6; } FALLTHROUGH;
- case 6: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_UnfairSemaphoreSpinLimit, false, W("System.Threading.ThreadPool.UnfairSemaphoreSpinLimit"))) { return 7; } FALLTHROUGH;
+ case 5: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_UnfairSemaphoreSpinLimit, false, W("System.Threading.ThreadPool.UnfairSemaphoreSpinLimit"))) { return 6; } FALLTHROUGH;
- case 7: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_Disable, true, W("System.Threading.ThreadPool.HillClimbing.Disable"))) { return 8; } FALLTHROUGH;
- case 8: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WavePeriod, false, W("System.Threading.ThreadPool.HillClimbing.WavePeriod"))) { return 9; } FALLTHROUGH;
- case 9: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_TargetSignalToNoiseRatio, false, W("System.Threading.ThreadPool.HillClimbing.TargetSignalToNoiseRatio"))) { return 10; } FALLTHROUGH;
- case 10: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_ErrorSmoothingFactor, false, W("System.Threading.ThreadPool.HillClimbing.ErrorSmoothingFactor"))) { return 11; } FALLTHROUGH;
- case 11: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WaveMagnitudeMultiplier, false, W("System.Threading.ThreadPool.HillClimbing.WaveMagnitudeMultiplier"))) { return 12; } FALLTHROUGH;
- case 12: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxWaveMagnitude, false, W("System.Threading.ThreadPool.HillClimbing.MaxWaveMagnitude"))) { return 13; } FALLTHROUGH;
- case 13: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WaveHistorySize, false, W("System.Threading.ThreadPool.HillClimbing.WaveHistorySize"))) { return 14; } FALLTHROUGH;
- case 14: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_Bias, false, W("System.Threading.ThreadPool.HillClimbing.Bias"))) { return 15; } FALLTHROUGH;
- case 15: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSecond, false, W("System.Threading.ThreadPool.HillClimbing.MaxChangePerSecond"))) { return 16; } FALLTHROUGH;
- case 16: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSample, false, W("System.Threading.ThreadPool.HillClimbing.MaxChangePerSample"))) { return 17; } FALLTHROUGH;
- case 17: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxSampleErrorPercent, false, W("System.Threading.ThreadPool.HillClimbing.MaxSampleErrorPercent"))) { return 18; } FALLTHROUGH;
- case 18: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_SampleIntervalLow, false, W("System.Threading.ThreadPool.HillClimbing.SampleIntervalLow"))) { return 19; } FALLTHROUGH;
- case 19: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_SampleIntervalHigh, false, W("System.Threading.ThreadPool.HillClimbing.SampleIntervalHigh"))) { return 20; } FALLTHROUGH;
- case 20: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_GainExponent, false, W("System.Threading.ThreadPool.HillClimbing.GainExponent"))) { return 21; } FALLTHROUGH;
+ case 6: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_Disable, true, W("System.Threading.ThreadPool.HillClimbing.Disable"))) { return 7; } FALLTHROUGH;
+ case 7: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WavePeriod, false, W("System.Threading.ThreadPool.HillClimbing.WavePeriod"))) { return 8; } FALLTHROUGH;
+ case 8: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_TargetSignalToNoiseRatio, false, W("System.Threading.ThreadPool.HillClimbing.TargetSignalToNoiseRatio"))) { return 9; } FALLTHROUGH;
+ case 9: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_ErrorSmoothingFactor, false, W("System.Threading.ThreadPool.HillClimbing.ErrorSmoothingFactor"))) { return 10; } FALLTHROUGH;
+ case 10: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WaveMagnitudeMultiplier, false, W("System.Threading.ThreadPool.HillClimbing.WaveMagnitudeMultiplier"))) { return 11; } FALLTHROUGH;
+ case 11: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxWaveMagnitude, false, W("System.Threading.ThreadPool.HillClimbing.MaxWaveMagnitude"))) { return 12; } FALLTHROUGH;
+ case 12: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WaveHistorySize, false, W("System.Threading.ThreadPool.HillClimbing.WaveHistorySize"))) { return 13; } FALLTHROUGH;
+ case 13: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_Bias, false, W("System.Threading.ThreadPool.HillClimbing.Bias"))) { return 14; } FALLTHROUGH;
+ case 14: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSecond, false, W("System.Threading.ThreadPool.HillClimbing.MaxChangePerSecond"))) { return 15; } FALLTHROUGH;
+ case 15: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSample, false, W("System.Threading.ThreadPool.HillClimbing.MaxChangePerSample"))) { return 16; } FALLTHROUGH;
+ case 16: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxSampleErrorPercent, false, W("System.Threading.ThreadPool.HillClimbing.MaxSampleErrorPercent"))) { return 17; } FALLTHROUGH;
+ case 17: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_SampleIntervalLow, false, W("System.Threading.ThreadPool.HillClimbing.SampleIntervalLow"))) { return 18; } FALLTHROUGH;
+ case 18: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_SampleIntervalHigh, false, W("System.Threading.ThreadPool.HillClimbing.SampleIntervalHigh"))) { return 19; } FALLTHROUGH;
+ case 19: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_GainExponent, false, W("System.Threading.ThreadPool.HillClimbing.GainExponent"))) { return 20; } FALLTHROUGH;
default:
*configValueRef = 0;
--- /dev/null
+<linker>
+ <assembly fullname="System.Private.CoreLib">
+ <type fullname="System.Threading.ThreadPool">
+ <method signature="System.Boolean get_UseWindowsThreadPool()" body="stub" value="true" feature="System.Threading.ThreadPool.UseWindowsThreadPool" featurevalue="true"/>
+ <method signature="System.Boolean get_UseWindowsThreadPool()" body="stub" value="false" feature="System.Threading.ThreadPool.UseWindowsThreadPool" featurevalue="false"/>
+ </type>
+ </assembly>
+</linker>
namespace Microsoft.Win32.SafeHandles
{
- internal class SafeThreadPoolIOHandle : SafeHandle
+ internal sealed class SafeThreadPoolIOHandle : SafeHandle
{
public SafeThreadPoolIOHandle()
: base(IntPtr.Zero, true)
<ILLinkSubstitutionsXmls Include="$(ILLinkSharedDirectory)ILLink.Substitutions.NoArmIntrinsics.xml" Condition="'$(SupportsArmIntrinsics)' != 'true'" />
<ILLinkSubstitutionsXmls Include="$(ILLinkSharedDirectory)ILLink.Substitutions.NoX86Intrinsics.xml" Condition="'$(SupportsX86Intrinsics)' != 'true'" />
<ILLinkSubstitutionsXmls Include="$(ILLinkSharedDirectory)ILLink.Substitutions.OSX.xml" Condition="'$(IsOSXLike)' == 'true'" />
+ <ILLinkSubstitutionsXmls Include="$(ILLinkSharedDirectory)ILLink.Substitutions.Windows.xml" Condition="'$(TargetsWindows)' == 'true'" />
<ILLinkSubstitutionsXmls Include="$(ILLinkSharedDirectory)ILLink.Substitutions.Browser.xml" Condition="'$(TargetsBrowser)' == 'true'" />
<ILLinkLinkAttributesXmls Include="$(ILLinkSharedDirectory)ILLink.LinkAttributes.Shared.xml" />
</ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\WaitHandle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\WaitHandleCannotBeOpenedException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\WaitHandleExtensions.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Win32ThreadPoolNativeOverlapped.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Win32ThreadPoolNativeOverlapped.ExecutionContextCallbackArgs.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Win32ThreadPoolNativeOverlapped.OverlappedData.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\ThreadStaticAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\ThrowHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\TimeOnly.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CancelIoEx.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.CancelIoEx.cs</Link>
</Compile>
- <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CompletionPort.cs" Condition="'$(FeaturePortableThreadPool)' == 'true'">
+ <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CompletionPort.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.CompletionPort.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.ConditionVariable.cs">
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CreateFile.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.CreateFile.cs</Link>
</Compile>
+ <Compile Include="$(CommonPath)\Interop\Windows\Kernel32\Interop.Timer.cs">
+ <Link>Interop\Windows\Kernel32\Interop.Timer.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Interop\Windows\Kernel32\Interop.ThreadPoolIO.cs">
+ <Link>Interop\Windows\Kernel32\Interop.ThreadPoolIO.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Interop\Windows\Kernel32\Interop.ThreadPool.cs">
+ <Link>Interop\Windows\Kernel32\Interop.ThreadPool.cs</Link>
+ </Compile>
<Compile Include="$(CommonPath)Interop\Windows\NtDll\Interop.NtCreateFile.cs">
<Link>Common\Interop\Windows\NtDll\Interop.NtCreateFile.cs</Link>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeFileHandle.OverlappedValueTaskSource.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeFindHandle.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeRegistryHandle.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeThreadPoolIOHandle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\AppDomain.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffer.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\DateTime.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\StandardOleMarshalObject.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Security\SecureString.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\LowLevelMonitor.Windows.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\RegisteredWaitHandle.WindowsThreadPool.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Thread.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\TimerQueue.Windows.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\TimerQueue.WindowsThreadPool.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPool.Windows.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolBoundHandle.WindowsThreadPool.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\PreAllocatedOverlapped.WindowsThreadPool.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\WindowsThreadPool.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\TimeZoneInfo.Win32.cs" />
</ItemGroup>
<ItemGroup Condition="'$(FeatureComWrappers)' != 'true'">
<Compile Include="$(MSBuildThisFileDirectory)System\Security\SecureString.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\LowLevelMonitor.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Thread.Unix.cs" />
- <Compile Include="$(MSBuildThisFileDirectory)System\Threading\TimerQueue.Unix.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\TimerQueue.Unix.cs" Condition="'$(FeaturePortableTimer)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\TimeZoneInfo.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\TimeZoneInfo.Unix.Android.cs" Condition="'$(TargetsAndroid)' == 'true' or '$(TargetsLinuxBionic)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\TimeZoneInfo.Unix.NonAndroid.cs" Condition="'$(TargetsAndroid)' != 'true' and '$(TargetsLinuxBionic)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Intrinsics\Wasm\PackedSimd.PlatformNotSupported.cs" />
</ItemGroup>
<ItemGroup Condition="'$(FeaturePortableThreadPool)' == 'true'">
- <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPool.Portable.cs" Condition="'$(FeatureCoreCLR)' != 'true'" />
- <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPool.Portable.Windows.cs" Condition="'$(TargetsWindows)' == 'true' and '$(FeatureCoreCLR)' != 'true'" />
- <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPool.Portable.Unix.cs" Condition="'$(TargetsUnix)' == 'true' or '$(TargetsBrowser)' == 'true' or '$(TargetsWasi)' == 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\CompleteWaitThreadPoolWorkItem.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPool.Portable.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPool.Unix.cs" Condition="'$(TargetsUnix)' == 'true' or '$(TargetsBrowser)' == 'true' or '$(TargetsWasi)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\NativeRuntimeEventSource.PortableThreadPool.cs" Condition="'$(FeatureCoreCLR)' != 'true' and '$(FeatureMono)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\NativeRuntimeEventSource.PortableThreadPool.NativeSinks.cs" Condition="'$(FeatureCoreCLR)' == 'true' or '$(FeatureMono)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\LowLevelLifoSemaphore.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\LowLevelLifoSemaphore.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
- <Compile Include="$(MSBuildThisFileDirectory)System\Threading\LowLevelLifoSemaphoreBase.cs" />
- <Compile Include="$(MSBuildThisFileDirectory)System\Threading\PreAllocatedOverlapped.cs" Condition="('$(TargetsBrowser)' != 'true' and '$(TargetsWasi)' != 'true') or '$(FeatureWasmThreads)' == 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\PreAllocatedOverlapped.Windows.cs" Condition="'$(TargetsWindows)' == 'true'"/>
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\PreAllocatedOverlapped.Unix.cs" Condition="'$(TargetsUnix)' == 'true' or '$(TargetsBrowser)' == 'true' or '$(TargetsWasi)' == 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\PreAllocatedOverlapped.Portable.cs" Condition="('$(TargetsBrowser)' != 'true' and '$(TargetsWasi)' != 'true') or '$(FeatureWasmThreads)' == 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\RegisteredWaitHandle.Windows.cs" Condition="'$(TargetsWindows)' == 'true'"/>
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\RegisteredWaitHandle.Unix.cs" Condition="'$(TargetsWindows)' != 'true'"/>
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\RegisteredWaitHandle.Portable.cs" />
- <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolBoundHandle.cs" />
- <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolBoundHandle.Unix.cs" Condition="'$(TargetsUnix)' == 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\LowLevelLifoSemaphoreBase.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolBoundHandle.Portable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolBoundHandle.Unix.cs" Condition="'$(TargetsWindows)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolBoundHandle.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolBoundHandleOverlapped.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolCallbackWrapper.cs" Condition="'$(FeatureNativeAot)' != 'true'" />
</ItemGroup>
<ItemGroup Condition="'$(FeatureObjCMarshal)' == 'true'">
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\ObjectiveC\ObjectiveCMarshal.cs" />
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
-using System.Diagnostics;
namespace System.Threading
{
- public static partial class ThreadPool
+ internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkItem
{
- [Conditional("unnecessary")]
- internal static void ReportThreadStatus(bool isWorking)
- {
- }
+ void IThreadPoolWorkItem.Execute() => PortableThreadPool.CompleteWait(_registeredWaitHandle, _timedOut);
}
}
_memoryUsageBytes = Math.Min(gcMemoryInfo.MemoryLoadBytes, gcMemoryInfo.HighMemoryLoadThresholdBytes);
return true; // continue receiving gen 2 GC callbacks
}
+
+ internal static RegisteredWaitHandle RegisterWaitForSingleObject(
+ WaitHandle waitObject,
+ WaitOrTimerCallback callBack,
+ object? state,
+ uint millisecondsTimeOutInterval,
+ bool executeOnlyOnce,
+ bool flowExecutionContext)
+ {
+ ArgumentNullException.ThrowIfNull(waitObject);
+ ArgumentNullException.ThrowIfNull(callBack);
+
+ RegisteredWaitHandle registeredWaitHandle = new RegisteredWaitHandle(
+ waitObject,
+ new _ThreadPoolWaitOrTimerCallback(callBack, state, flowExecutionContext),
+ (int)millisecondsTimeOutInterval,
+ !executeOnlyOnce);
+
+ PortableThreadPool.ThreadPoolInstance.RegisterWaitHandle(registeredWaitHandle);
+
+ return registeredWaitHandle;
+ }
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Threading
+{
+ /// <summary>
+ /// Represents pre-allocated state for native overlapped I/O operations.
+ /// </summary>
+ /// <seealso cref="ThreadPoolBoundHandle.AllocateNativeOverlapped(PreAllocatedOverlapped)"/>
+ public sealed partial class PreAllocatedOverlapped : IDisposable, IDeferredDisposable
+ {
+ internal ThreadPoolBoundHandleOverlapped? _overlappedPortableCore;
+
+ private static PreAllocatedOverlapped UnsafeCreatePortableCore(IOCompletionCallback callback, object? state, object? pinData) =>
+ new PreAllocatedOverlapped(callback, state, pinData, flowExecutionContext: false);
+
+ private bool AddRefPortableCore()
+ {
+ return _lifetime.AddRef();
+ }
+
+ private void ReleasePortableCore()
+ {
+ _lifetime.Release(this);
+ }
+
+ private void DisposePortableCore()
+ {
+ _lifetime.Dispose(this);
+ GC.SuppressFinalize(this);
+ }
+
+ private unsafe void IDeferredDisposableOnFinalReleasePortableCore(bool disposed)
+ {
+ if (_overlappedPortableCore != null) // protect against ctor throwing exception and leaving field uninitialized
+ {
+ if (disposed)
+ {
+ Overlapped.Free(_overlappedPortableCore._nativeOverlapped);
+ }
+ else
+ {
+ _overlappedPortableCore._boundHandle = null;
+ _overlappedPortableCore._completed = false;
+ *_overlappedPortableCore._nativeOverlapped = default;
+ }
+ }
+ }
+ }
+}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics;
+
namespace System.Threading
{
/// <summary>
/// Represents pre-allocated state for native overlapped I/O operations.
/// </summary>
/// <seealso cref="ThreadPoolBoundHandle.AllocateNativeOverlapped(PreAllocatedOverlapped)"/>
- public sealed class PreAllocatedOverlapped : IDisposable, IDeferredDisposable
+ public sealed partial class PreAllocatedOverlapped : IDisposable, IDeferredDisposable
{
- internal readonly ThreadPoolBoundHandleOverlapped _overlapped;
- private DeferredDisposableLifetime<PreAllocatedOverlapped> _lifetime;
-
/// <summary>
/// Initializes a new instance of the <see cref="PreAllocatedOverlapped"/> class, specifying
/// a delegate that is invoked when each asynchronous I/O operation is complete, a user-provided
{
}
+ private DeferredDisposableLifetime<PreAllocatedOverlapped> _lifetime;
+
/// <summary>
/// Initializes a new instance of the <see cref="PreAllocatedOverlapped"/> class, specifying
/// a delegate that is invoked when each asynchronous I/O operation is complete, a user-provided
/// </exception>
[CLSCompliant(false)]
public static PreAllocatedOverlapped UnsafeCreate(IOCompletionCallback callback, object? state, object? pinData) =>
- new PreAllocatedOverlapped(callback, state, pinData, flowExecutionContext: false);
+ UnsafeCreatePortableCore(callback, state, pinData);
- private PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData, bool flowExecutionContext)
+ private unsafe PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData, bool flowExecutionContext)
{
+ // This construction is duplicated in PreAllocatedOverlapped.Windows.cs
+ // It has to either be duplicated or remove the 'readonly' part of _overlappedPortableCore
ArgumentNullException.ThrowIfNull(callback);
- _overlapped = new ThreadPoolBoundHandleOverlapped(callback, state, pinData, this, flowExecutionContext);
- }
-
- internal bool AddRef()
- {
- return _lifetime.AddRef();
+ _overlappedPortableCore = new ThreadPoolBoundHandleOverlapped(callback, state, pinData, this, flowExecutionContext);
}
- internal void Release()
- {
- _lifetime.Release(this);
- }
+ internal bool AddRef() => AddRefPortableCore();
/// <summary>
/// Frees the resources associated with this <see cref="PreAllocatedOverlapped"/> instance.
/// </summary>
public void Dispose()
{
- _lifetime.Dispose(this);
- GC.SuppressFinalize(this);
+ DisposePortableCore();
}
- ~PreAllocatedOverlapped()
+ internal void Release()
{
- Dispose();
+ ReleasePortableCore();
}
unsafe void IDeferredDisposable.OnFinalRelease(bool disposed)
{
- if (_overlapped != null) // protect against ctor throwing exception and leaving field uninitialized
- {
- if (disposed)
- {
- Overlapped.Free(_overlapped._nativeOverlapped);
- }
- else
- {
- _overlapped._boundHandle = null;
- _overlapped._completed = false;
- *_overlapped._nativeOverlapped = default;
- }
- }
+ IDeferredDisposableOnFinalReleasePortableCore(disposed);
}
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+
+namespace System.Threading
+{
+ /// <summary>
+ /// Represents pre-allocated state for native overlapped I/O operations.
+ /// </summary>
+ /// <seealso cref="ThreadPoolBoundHandle.AllocateNativeOverlapped(PreAllocatedOverlapped)"/>
+ public sealed partial class PreAllocatedOverlapped : IDisposable, IDeferredDisposable
+ {
+ private DeferredDisposableLifetime<PreAllocatedOverlapped> _lifetime;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PreAllocatedOverlapped"/> class, specifying
+ /// a delegate that is invoked when each asynchronous I/O operation is complete, a user-provided
+ /// object providing context, and managed objects that serve as buffers.
+ /// </summary>
+ /// <param name="callback">
+ /// An <see cref="IOCompletionCallback"/> delegate that represents the callback method
+ /// invoked when each asynchronous I/O operation completes.
+ /// </param>
+ /// <param name="state">
+ /// A user-provided object that distinguishes <see cref="NativeOverlapped"/> instance produced from this
+ /// object from other <see cref="NativeOverlapped"/> instances. Can be <see langword="null"/>.
+ /// </param>
+ /// <param name="pinData">
+ /// An object or array of objects representing the input or output buffer for the operations. Each
+ /// object represents a buffer, for example an array of bytes. Can be <see langword="null"/>.
+ /// </param>
+ /// <remarks>
+ /// The new <see cref="PreAllocatedOverlapped"/> instance can be passed to
+ /// <see cref="ThreadPoolBoundHandle.AllocateNativeOverlapped(PreAllocatedOverlapped)"/>, to produce
+ /// a <see cref="NativeOverlapped"/> instance that can be passed to the operating system in overlapped
+ /// I/O operations. A single <see cref="PreAllocatedOverlapped"/> instance can only be used for
+ /// a single native I/O operation at a time. However, the state stored in the <see cref="PreAllocatedOverlapped"/>
+ /// instance can be reused for subsequent native operations.
+ /// <note>
+ /// The buffers specified in <paramref name="pinData"/> are pinned until <see cref="Dispose"/> is called.
+ /// </note>
+ /// </remarks>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="callback"/> is <see langword="null"/>.
+ /// </exception>
+ /// <exception cref="ObjectDisposedException">
+ /// This method was called after the <see cref="ThreadPoolBoundHandle"/> was disposed.
+ /// </exception>
+ [CLSCompliant(false)]
+ public PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData) :
+ this(callback, state, pinData, flowExecutionContext: true)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PreAllocatedOverlapped"/> class, specifying
+ /// a delegate that is invoked when each asynchronous I/O operation is complete, a user-provided
+ /// object providing context, and managed objects that serve as buffers.
+ /// </summary>
+ /// <param name="callback">
+ /// An <see cref="IOCompletionCallback"/> delegate that represents the callback method
+ /// invoked when each asynchronous I/O operation completes.
+ /// </param>
+ /// <param name="state">
+ /// A user-provided object that distinguishes <see cref="NativeOverlapped"/> instance produced from this
+ /// object from other <see cref="NativeOverlapped"/> instances. Can be <see langword="null"/>.
+ /// </param>
+ /// <param name="pinData">
+ /// An object or array of objects representing the input or output buffer for the operations. Each
+ /// object represents a buffer, for example an array of bytes. Can be <see langword="null"/>.
+ /// </param>
+ /// <remarks>
+ /// The new <see cref="PreAllocatedOverlapped"/> instance can be passed to
+ /// <see cref="ThreadPoolBoundHandle.AllocateNativeOverlapped(PreAllocatedOverlapped)"/>, to produce
+ /// a <see cref="NativeOverlapped"/> instance that can be passed to the operating system in overlapped
+ /// I/O operations. A single <see cref="PreAllocatedOverlapped"/> instance can only be used for
+ /// a single native I/O operation at a time. However, the state stored in the <see cref="PreAllocatedOverlapped"/>
+ /// instance can be reused for subsequent native operations. ExecutionContext is not flowed to the invocation
+ /// of the callback.
+ /// <note>
+ /// The buffers specified in <paramref name="pinData"/> are pinned until <see cref="Dispose"/> is called.
+ /// </note>
+ /// </remarks>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="callback"/> is <see langword="null"/>.
+ /// </exception>
+ /// <exception cref="ObjectDisposedException">
+ /// This method was called after the <see cref="ThreadPoolBoundHandle"/> was disposed.
+ /// </exception>
+ [CLSCompliant(false)]
+ public static PreAllocatedOverlapped UnsafeCreate(IOCompletionCallback callback, object? state, object? pinData) =>
+ ThreadPool.UseWindowsThreadPool ? UnsafeCreateWindowsThreadPool(callback, state, pinData) : UnsafeCreatePortableCore(callback, state, pinData);
+
+ private unsafe PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData, bool flowExecutionContext)
+ {
+ if (ThreadPool.UseWindowsThreadPool)
+ {
+ ArgumentNullException.ThrowIfNull(callback);
+
+ _overlappedWindowsThreadPool = Win32ThreadPoolNativeOverlapped.Allocate(callback, state, pinData, this, flowExecutionContext);
+ }
+ else
+ {
+ // This construction is duplicated in PreAllocatedOverlapped.Unix.cs
+ // It has to either be duplicated or remove the 'readonly' part of _overlappedPortableCore
+ ArgumentNullException.ThrowIfNull(callback);
+
+ _overlappedPortableCore = new ThreadPoolBoundHandleOverlapped(callback, state, pinData, this, flowExecutionContext);
+ }
+ }
+
+ internal bool AddRef() => ThreadPool.UseWindowsThreadPool ? AddRefWindowsThreadPool() : AddRefPortableCore();
+
+ internal void Release()
+ {
+ if (ThreadPool.UseWindowsThreadPool)
+ {
+ ReleaseWindowsThreadPool();
+ }
+ else
+ {
+ ReleasePortableCore();
+ }
+ }
+
+ /// <summary>
+ /// Frees the resources associated with this <see cref="PreAllocatedOverlapped"/> instance.
+ /// </summary>
+ public void Dispose()
+ {
+ if (ThreadPool.UseWindowsThreadPool)
+ {
+ DisposeWindowsThreadPool();
+ }
+ else
+ {
+ DisposePortableCore();
+ }
+ }
+
+ ~PreAllocatedOverlapped()
+ {
+ Dispose();
+ }
+
+ unsafe void IDeferredDisposable.OnFinalRelease(bool disposed)
+ {
+ if (ThreadPool.UseWindowsThreadPool)
+ {
+ IDeferredDisposableOnFinalReleaseWindowsThreadPool(disposed);
+ }
+ else
+ {
+ IDeferredDisposableOnFinalReleasePortableCore(disposed);
+ }
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+
+namespace System.Threading
+{
+ public sealed partial class PreAllocatedOverlapped : IDisposable, IDeferredDisposable
+ {
+ internal readonly unsafe Win32ThreadPoolNativeOverlapped* _overlappedWindowsThreadPool;
+
+ private static PreAllocatedOverlapped UnsafeCreateWindowsThreadPool(IOCompletionCallback callback, object? state, object? pinData) =>
+ new PreAllocatedOverlapped(callback, state, pinData, flowExecutionContext: false);
+
+ private bool AddRefWindowsThreadPool()
+ {
+ return _lifetime.AddRef();
+ }
+
+ private void ReleaseWindowsThreadPool()
+ {
+ _lifetime.Release(this);
+ }
+
+ internal unsafe bool IsUserObject(byte[]? buffer) => _overlappedWindowsThreadPool->IsUserObject(buffer);
+
+ private void DisposeWindowsThreadPool()
+ {
+ _lifetime.Dispose(this);
+ GC.SuppressFinalize(this);
+ }
+
+ private unsafe void IDeferredDisposableOnFinalReleaseWindowsThreadPool(bool disposed)
+ {
+ if (_overlappedWindowsThreadPool != null)
+ {
+ if (disposed)
+ Win32ThreadPoolNativeOverlapped.Free(_overlappedWindowsThreadPool);
+ else
+ *Win32ThreadPoolNativeOverlapped.ToNativeOverlapped(_overlappedWindowsThreadPool) = default(NativeOverlapped);
+ }
+ }
+ }
+}
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
#endif
public sealed partial class RegisteredWaitHandle : MarshalByRefObject
{
- internal RegisteredWaitHandle(WaitHandle waitHandle, _ThreadPoolWaitOrTimerCallback callbackHelper,
- int millisecondsTimeout, bool repeating)
- {
- Thread.ThrowIfNoThreadStart();
- Handle = waitHandle.SafeWaitHandle;
- Callback = callbackHelper;
- TimeoutDurationMs = millisecondsTimeout;
- Repeating = repeating;
- if (!IsInfiniteTimeout)
- {
- RestartTimeout();
- }
- }
-
private static AutoResetEvent? s_cachedEvent;
-
- private static AutoResetEvent RentEvent() =>
- Interlocked.Exchange(ref s_cachedEvent, null) ??
- new AutoResetEvent(false);
-
- private static void ReturnEvent(AutoResetEvent resetEvent)
- {
- if (Interlocked.CompareExchange(ref s_cachedEvent, resetEvent, null) != null)
- {
- resetEvent.Dispose();
- }
- }
-
private static readonly LowLevelLock s_callbackLock = new LowLevelLock();
/// <summary>
- /// The callback to execute when the wait on <see cref="Handle"/> either times out or completes.
- /// </summary>
- internal _ThreadPoolWaitOrTimerCallback Callback { get; }
-
- /// <summary>
- /// The <see cref="SafeWaitHandle"/> that was registered.
- /// </summary>
- internal SafeWaitHandle Handle { get; }
-
- /// <summary>
- /// The time this handle times out at in ms.
- /// </summary>
- internal int TimeoutTimeMs { get; private set; }
-
- internal int TimeoutDurationMs { get; }
-
- internal bool IsInfiniteTimeout => TimeoutDurationMs == -1;
-
- internal void RestartTimeout()
- {
- Debug.Assert(!IsInfiniteTimeout);
- TimeoutTimeMs = Environment.TickCount + TimeoutDurationMs;
- }
-
- /// <summary>
- /// Whether or not the wait is a repeating wait.
- /// </summary>
- internal bool Repeating { get; }
-
- /// <summary>
/// The <see cref="WaitHandle"/> the user passed in via <see cref="Unregister(WaitHandle)"/>.
/// </summary>
private SafeWaitHandle? UserUnregisterWaitHandle { get; set; }
/// </summary>
internal PortableThreadPool.WaitThread? WaitThread { get; set; }
- public bool Unregister(WaitHandle waitObject)
+ internal RegisteredWaitHandle(WaitHandle waitHandle, _ThreadPoolWaitOrTimerCallback callbackHelper,
+ int millisecondsTimeout, bool repeating)
+ {
+#if WINDOWS
+ Debug.Assert(!ThreadPool.UseWindowsThreadPool);
+#endif
+ GC.SuppressFinalize(this);
+
+ Thread.ThrowIfNoThreadStart();
+ _waitHandle = waitHandle.SafeWaitHandle;
+ _callbackHelper = callbackHelper;
+ _signedMillisecondsTimeout = millisecondsTimeout;
+ _repeating = repeating;
+ if (!IsInfiniteTimeout)
+ {
+ RestartTimeout();
+ }
+ }
+
+ private static AutoResetEvent RentEvent() =>
+ Interlocked.Exchange(ref s_cachedEvent, null) ??
+ new AutoResetEvent(false);
+
+ private static void ReturnEvent(AutoResetEvent resetEvent)
+ {
+ if (Interlocked.CompareExchange(ref s_cachedEvent, resetEvent, null) != null)
+ {
+ resetEvent.Dispose();
+ }
+ }
+
+ internal void RestartTimeout()
+ {
+ Debug.Assert(!IsInfiniteTimeout);
+ TimeoutTimeMs = Environment.TickCount + TimeoutDurationMs;
+ }
+
+ private bool UnregisterPortableCore(WaitHandle waitObject)
{
// The registered wait handle must have been registered by this time, otherwise the instance is not handed out to
// the caller of the public variants of RegisterWaitForSingleObject
/// Perform the registered callback if the <see cref="UserUnregisterWaitHandle"/> has not been signaled.
/// </summary>
/// <param name="timedOut">Whether or not the wait timed out.</param>
- internal void PerformCallback(bool timedOut)
+ internal void PerformCallbackPortableCore(bool timedOut)
{
#if DEBUG
s_callbackLock.Acquire();
}
#endif
- _ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(Callback, timedOut);
+ _ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(Callback!, timedOut);
CompleteCallbackRequest();
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Runtime.Versioning;
+using Microsoft.Win32.SafeHandles;
+
+namespace System.Threading
+{
+ /// <summary>
+ /// An object representing the registration of a <see cref="WaitHandle"/> via <see cref="ThreadPool.RegisterWaitForSingleObject"/>.
+ /// </summary>
+ public sealed partial class RegisteredWaitHandle : MarshalByRefObject
+ {
+ private SafeWaitHandle? _waitHandle;
+ private readonly _ThreadPoolWaitOrTimerCallback? _callbackHelper;
+ private readonly int _signedMillisecondsTimeout;
+ private bool _repeating;
+
+ /// <summary>
+ /// The callback to execute when the wait on <see cref="Handle"/> either times out or completes.
+ /// </summary>
+ internal _ThreadPoolWaitOrTimerCallback? Callback
+ {
+ get => _callbackHelper;
+ }
+
+ /// <summary>
+ /// The <see cref="SafeWaitHandle"/> that was registered.
+ /// </summary>
+ internal SafeWaitHandle Handle
+ {
+ get
+ {
+ Debug.Assert(_waitHandle != null);
+ return _waitHandle;
+ }
+ }
+
+ /// <summary>
+ /// The time this handle times out at in ms.
+ /// </summary>
+ internal int TimeoutTimeMs { get; private set; }
+
+ internal int TimeoutDurationMs
+ {
+ get => _signedMillisecondsTimeout;
+ }
+
+ internal bool IsInfiniteTimeout => TimeoutDurationMs == -1;
+
+ /// <summary>
+ /// Whether or not the wait is a repeating wait.
+ /// </summary>
+ internal bool Repeating
+ {
+ get => _repeating;
+ }
+
+ public bool Unregister(WaitHandle waitObject) => UnregisterPortableCore(waitObject);
+
+ /// <summary>
+ /// Perform the registered callback if the <see cref="UserUnregisterWaitHandle"/> has not been signaled.
+ /// </summary>
+ /// <param name="timedOut">Whether or not the wait timed out.</param>
+ internal void PerformCallback(bool timedOut)
+ {
+ PerformCallbackPortableCore(timedOut);
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Win32.SafeHandles;
+using System.Diagnostics;
+using System.Runtime;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace System.Threading
+{
+ public sealed partial class RegisteredWaitHandle : MarshalByRefObject
+ {
+ private SafeWaitHandle? _waitHandle;
+ private readonly _ThreadPoolWaitOrTimerCallback? _callbackHelper;
+ private readonly uint _millisecondsTimeout;
+ private readonly int _signedMillisecondsTimeout;
+ private bool _repeating;
+
+ /// <summary>
+ /// The callback to execute when the wait on <see cref="Handle"/> either times out or completes.
+ /// </summary>
+ internal _ThreadPoolWaitOrTimerCallback? Callback
+ {
+ get => _callbackHelper;
+ }
+
+ /// <summary>
+ /// The <see cref="SafeWaitHandle"/> that was registered.
+ /// </summary>
+ internal SafeWaitHandle Handle
+ {
+ get
+ {
+ Debug.Assert(_waitHandle != null);
+ return _waitHandle;
+ }
+ }
+
+ /// <summary>
+ /// The time this handle times out at in ms.
+ /// </summary>
+ internal int TimeoutTimeMs { get; private set; }
+
+ internal int TimeoutDurationMs
+ {
+ get => _signedMillisecondsTimeout;
+ }
+
+ internal bool IsInfiniteTimeout => TimeoutDurationMs == -1;
+
+ /// <summary>
+ /// Whether or not the wait is a repeating wait.
+ /// </summary>
+ internal bool Repeating
+ {
+ get => _repeating;
+ }
+
+ public bool Unregister(WaitHandle waitObject) =>
+ ThreadPool.UseWindowsThreadPool ?
+ UnregisterWindowsThreadPool(waitObject) :
+ UnregisterPortableCore(waitObject);
+
+ /// <summary>
+ /// Perform the registered callback if the <see cref="UserUnregisterWaitHandle"/> has not been signaled.
+ /// </summary>
+ /// <param name="timedOut">Whether or not the wait timed out.</param>
+ internal void PerformCallback(bool timedOut)
+ {
+ if (ThreadPool.UseWindowsThreadPool)
+ {
+ PerformCallbackWindowsThreadPool(timedOut);
+ }
+ else
+ {
+ PerformCallbackPortableCore(timedOut);
+ }
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Win32.SafeHandles;
+using System.Diagnostics;
+using System.Runtime;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace System.Threading
+{
+ //
+ // Windows-specific implementation of ThreadPool
+ //
+ public sealed partial class RegisteredWaitHandle : MarshalByRefObject
+ {
+ private readonly object? _lock;
+ private bool _unregistering;
+
+ // Handle to this object to keep it alive
+ private GCHandle _gcHandle;
+
+ // Pointer to the TP_WAIT structure
+ private IntPtr _tpWait;
+
+ internal unsafe RegisteredWaitHandle(SafeWaitHandle waitHandle, _ThreadPoolWaitOrTimerCallback callbackHelper,
+ uint millisecondsTimeout, bool repeating)
+ {
+ Debug.Assert(ThreadPool.UseWindowsThreadPool);
+
+ _lock = new object();
+
+ waitHandle.DangerousAddRef();
+ _waitHandle = waitHandle;
+
+ _callbackHelper = callbackHelper;
+ _millisecondsTimeout = millisecondsTimeout;
+ _repeating = repeating;
+
+ // Allocate _gcHandle and _tpWait as the last step and make sure they are never leaked
+ _gcHandle = GCHandle.Alloc(this);
+
+ _tpWait = Interop.Kernel32.CreateThreadpoolWait(&RegisteredWaitCallback, (IntPtr)_gcHandle, IntPtr.Zero);
+
+ if (_tpWait == IntPtr.Zero)
+ {
+ _gcHandle.Free();
+ throw new OutOfMemoryException();
+ }
+ }
+
+#pragma warning disable IDE0060 // Remove unused parameter
+ [UnmanagedCallersOnly]
+ internal static void RegisteredWaitCallback(IntPtr instance, IntPtr context, IntPtr wait, uint waitResult)
+ {
+ var wrapper = ThreadPoolCallbackWrapper.Enter();
+
+ GCHandle handle = (GCHandle)context;
+ RegisteredWaitHandle registeredWaitHandle = (RegisteredWaitHandle)handle.Target!;
+ Debug.Assert((handle == registeredWaitHandle._gcHandle) && (wait == registeredWaitHandle._tpWait));
+
+ bool timedOut = (waitResult == (uint)Interop.Kernel32.WAIT_TIMEOUT);
+ registeredWaitHandle.PerformCallbackWindowsThreadPool(timedOut);
+ ThreadPool.IncrementCompletedWorkItemCount();
+
+ wrapper.Exit();
+ }
+#pragma warning restore IDE0060
+
+ private void PerformCallbackWindowsThreadPool(bool timedOut)
+ {
+ // Prevent the race condition with Unregister and the previous PerformCallback call, which may still be
+ // holding the _lock.
+ // If another thread is running Unregister, no need to restart the timer or clean up
+ lock (_lock!)
+ {
+ if (!_unregistering)
+ {
+ if (_repeating)
+ {
+ // Allow this wait to fire again. Restart the timer before executing the callback.
+ RestartWait();
+ }
+ else
+ {
+ // This wait will not be fired again. Free the GC handle to allow the GC to collect this object.
+ Debug.Assert(_gcHandle.IsAllocated);
+ _gcHandle.Free();
+ }
+ }
+ }
+
+ _ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(_callbackHelper!, timedOut);
+ }
+
+ internal unsafe void RestartWait()
+ {
+ long timeout;
+ long* pTimeout = null; // Null indicates infinite timeout
+
+ if (_millisecondsTimeout != Timeout.UnsignedInfinite)
+ {
+ timeout = -10000L * _millisecondsTimeout;
+ pTimeout = &timeout;
+ }
+
+ // We can use DangerousGetHandle because of DangerousAddRef in the constructor
+ Interop.Kernel32.SetThreadpoolWait(_tpWait, _waitHandle!.DangerousGetHandle(), (IntPtr)pTimeout);
+ }
+
+ private bool UnregisterWindowsThreadPool(WaitHandle waitObject)
+ {
+ // Hold the lock during the synchronous part of Unregister (as in CoreCLR)
+ lock(_lock!)
+ {
+ if (!_unregistering)
+ {
+ // Ensure callbacks will not call SetThreadpoolWait anymore
+ _unregistering = true;
+
+ // Cease queueing more callbacks
+ Interop.Kernel32.SetThreadpoolWait(_tpWait, IntPtr.Zero, IntPtr.Zero);
+
+ // Should we wait for callbacks synchronously? Note that we treat the zero handle as the asynchronous case.
+ SafeWaitHandle? safeWaitHandle = waitObject?.SafeWaitHandle;
+ bool blocking = ((safeWaitHandle != null) && (safeWaitHandle.DangerousGetHandle() == new IntPtr(-1)));
+
+ if (blocking)
+ {
+ FinishUnregistering();
+ }
+ else
+ {
+ // Wait for callbacks and dispose resources asynchronously
+ ThreadPool.QueueUserWorkItem(FinishUnregisteringAsync, safeWaitHandle);
+ }
+
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void FinishUnregistering()
+ {
+ Debug.Assert(_unregistering);
+
+ // Wait for outstanding wait callbacks to complete
+ Interop.Kernel32.WaitForThreadpoolWaitCallbacks(_tpWait, false);
+
+ // Now it is safe to dispose resources
+ Interop.Kernel32.CloseThreadpoolWait(_tpWait);
+ _tpWait = IntPtr.Zero;
+
+ if (_gcHandle.IsAllocated)
+ {
+ _gcHandle.Free();
+ }
+
+ Debug.Assert(_waitHandle != null);
+ _waitHandle.DangerousRelease();
+ _waitHandle = null;
+
+ GC.SuppressFinalize(this);
+ }
+
+ private void FinishUnregisteringAsync(object? waitObject)
+ {
+ FinishUnregistering();
+
+ // Signal the provided wait object
+ SafeWaitHandle? safeWaitHandle = (SafeWaitHandle?)waitObject;
+
+ if ((safeWaitHandle != null) && !safeWaitHandle.IsInvalid)
+ {
+ Interop.Kernel32.SetEvent(safeWaitHandle);
+ }
+ }
+
+ ~RegisteredWaitHandle()
+ {
+ Debug.Assert(ThreadPool.UseWindowsThreadPool);
+ // If _gcHandle is allocated, it points to this object, so this object must not be collected by the GC
+ Debug.Assert(!_gcHandle.IsAllocated);
+
+ // If this object gets resurrected and another thread calls Unregister, that creates a race condition.
+ // Do not block the finalizer thread. If another thread is running Unregister, it will clean up for us.
+ // The _lock may be null in case of OOM in the constructor.
+ if ((_lock != null) && Monitor.TryEnter(_lock))
+ {
+ try
+ {
+ if (!_unregistering)
+ {
+ _unregistering = true;
+
+ if (_tpWait != IntPtr.Zero)
+ {
+ // There must be no in-flight callbacks; just dispose resources
+ Interop.Kernel32.CloseThreadpoolWait(_tpWait);
+ _tpWait = IntPtr.Zero;
+ }
+
+ if (_waitHandle != null)
+ {
+ _waitHandle.DangerousRelease();
+ _waitHandle = null;
+ }
+ }
+ }
+ finally
+ {
+ Monitor.Exit(_lock);
+ }
+ }
+ }
+ }
+
+}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Runtime.InteropServices;
-using System.Runtime.Versioning;
-
-namespace System.Threading
-{
- public static partial class ThreadPool
- {
- [CLSCompliant(false)]
- [SupportedOSPlatform("windows")]
- public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) =>
- throw new PlatformNotSupportedException(SR.PlatformNotSupported_OverlappedIO);
-
- [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")]
- [SupportedOSPlatform("windows")]
- public static bool BindHandle(IntPtr osHandle) =>
- throw new PlatformNotSupportedException(SR.PlatformNotSupported_OverlappedIO);
-
- [SupportedOSPlatform("windows")]
- public static bool BindHandle(SafeHandle osHandle) =>
- throw new PlatformNotSupportedException(SR.PlatformNotSupported_OverlappedIO);
- }
-}
{
public static partial class ThreadPool
{
- [CLSCompliant(false)]
[SupportedOSPlatform("windows")]
- public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped)
+ private static unsafe bool UnsafeQueueNativeOverlappedPortableCore(NativeOverlapped* overlapped)
{
if (overlapped == null)
{
[Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")]
[SupportedOSPlatform("windows")]
- public static bool BindHandle(IntPtr osHandle)
+ private static bool BindHandlePortableCore(IntPtr osHandle)
{
PortableThreadPool.ThreadPoolInstance.RegisterForIOCompletionNotifications(osHandle);
return true;
}
[SupportedOSPlatform("windows")]
- public static bool BindHandle(SafeHandle osHandle)
+ private static bool BindHandlePortableCore(SafeHandle osHandle)
{
ArgumentNullException.ThrowIfNull(osHandle);
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Diagnostics;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
namespace System.Threading
{
- //
- // Portable implementation of ThreadPool
- //
-
- internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkItem
- {
- void IThreadPoolWorkItem.Execute() => PortableThreadPool.CompleteWait(_registeredWaitHandle, _timedOut);
- }
-
public static partial class ThreadPool
{
- // Indicates whether the thread pool should yield the thread from the dispatch loop to the runtime periodically so that
- // the runtime may use the thread for processing other work
-#if !(TARGET_BROWSER && FEATURE_WASM_THREADS)
- internal static bool YieldFromDispatchLoop => false;
-#endif
-
#if NATIVEAOT
private const bool IsWorkerTrackingEnabledInConfig = false;
#else
private static readonly bool IsWorkerTrackingEnabledInConfig =
- AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.EnableWorkerTracking", false);
+ AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.EnableWorkerTracking", "DOTNET_ThreadPool_EnableWorkerTracking");
#endif
- // Threadpool specific initialization of a new thread. Used by OS-provided threadpools. No-op for portable threadpool.
- internal static void InitializeForThreadPoolThread() { }
+#if !(TARGET_BROWSER && FEATURE_WASM_THREADS)
+ // Indicates whether the thread pool should yield the thread from the dispatch loop to the runtime periodically so that
+ // the runtime may use the thread for processing other work.
+ internal static bool YieldFromDispatchLoop => false;
+#endif
-#pragma warning disable IDE0060
- internal static bool CanSetMinIOCompletionThreads(int ioCompletionThreads) => false;
- internal static bool CanSetMaxIOCompletionThreads(int ioCompletionThreads) => false;
-#pragma warning restore IDE0060
+#if !CORECLR
+ internal static bool EnsureConfigInitialized() => true;
+#endif
- [Conditional("unnecessary")]
- internal static void SetMinIOCompletionThreads(int ioCompletionThreads) { }
- [Conditional("unnecessary")]
- internal static void SetMaxIOCompletionThreads(int ioCompletionThreads) { }
+ internal static object GetOrCreateThreadLocalCompletionCountObject() =>
+ PortableThreadPool.ThreadPoolInstance.GetOrCreateThreadLocalCompletionCountObject();
public static bool SetMaxThreads(int workerThreads, int completionPortThreads) =>
PortableThreadPool.ThreadPoolInstance.SetMaxThreads(workerThreads, completionPortThreads);
- public static void GetMaxThreads(out int workerThreads, out int completionPortThreads) =>
+
+ public static void GetMaxThreads(out int workerThreads, out int completionPortThreads)
+ {
PortableThreadPool.ThreadPoolInstance.GetMaxThreads(out workerThreads, out completionPortThreads);
+ }
+
public static bool SetMinThreads(int workerThreads, int completionPortThreads) =>
PortableThreadPool.ThreadPoolInstance.SetMinThreads(workerThreads, completionPortThreads);
- public static void GetMinThreads(out int workerThreads, out int completionPortThreads) =>
+
+ public static void GetMinThreads(out int workerThreads, out int completionPortThreads)
+ {
PortableThreadPool.ThreadPoolInstance.GetMinThreads(out workerThreads, out completionPortThreads);
- public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads) =>
- PortableThreadPool.ThreadPoolInstance.GetAvailableThreads(out workerThreads, out completionPortThreads);
+ }
- /// <summary>
- /// Gets the number of thread pool threads that currently exist.
- /// </summary>
- /// <remarks>
- /// For a thread pool implementation that may have different types of threads, the count includes all types.
- /// </remarks>
- public static int ThreadCount => PortableThreadPool.ThreadPoolInstance.ThreadCount;
+ public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads)
+ {
+ PortableThreadPool.ThreadPoolInstance.GetAvailableThreads(out workerThreads, out completionPortThreads);
+ }
- /// <summary>
- /// Gets the number of work items that have been processed by the thread pool so far.
- /// </summary>
- /// <remarks>
- /// For a thread pool implementation that may have different types of work items, the count includes all types.
- /// </remarks>
- public static long CompletedWorkItemCount => PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static void NotifyWorkItemProgress()
+ {
+ PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress();
+ }
- /// <summary>
- /// This method is called to request a new thread pool worker to handle pending work.
- /// </summary>
- internal static void RequestWorkerThread() => PortableThreadPool.ThreadPoolInstance.RequestWorker();
+ internal static bool NotifyThreadBlocked() =>
+ PortableThreadPool.ThreadPoolInstance.NotifyThreadBlocked();
- internal static void NotifyWorkItemProgress() => PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress();
+ internal static void NotifyThreadUnblocked()
+ {
+ PortableThreadPool.ThreadPoolInstance.NotifyThreadUnblocked();
+ }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject, int currentTimeMs) =>
+ internal static bool NotifyWorkItemComplete(object threadLocalCompletionCountObject, int currentTimeMs) =>
PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete(threadLocalCompletionCountObject, currentTimeMs);
- internal static bool NotifyThreadBlocked() => PortableThreadPool.ThreadPoolInstance.NotifyThreadBlocked();
- internal static void NotifyThreadUnblocked() => PortableThreadPool.ThreadPoolInstance.NotifyThreadUnblocked();
+ /// <summary>
+ /// This method is called to request a new thread pool worker to handle pending work.
+ /// </summary>
+ internal static unsafe void RequestWorkerThread()
+ {
+ PortableThreadPool.ThreadPoolInstance.RequestWorker();
+ }
- internal static object GetOrCreateThreadLocalCompletionCountObject() =>
- PortableThreadPool.ThreadPoolInstance.GetOrCreateThreadLocalCompletionCountObject();
+ internal static void ReportThreadStatus(bool isWorking)
+ {
+ PortableThreadPool.ThreadPoolInstance.ReportThreadStatus(isWorking);
+ }
- private static RegisteredWaitHandle RegisterWaitForSingleObject(
+ internal static RegisteredWaitHandle RegisterWaitForSingleObject(
WaitHandle waitObject,
WaitOrTimerCallback callBack,
object? state,
bool executeOnlyOnce,
bool flowExecutionContext)
{
- ArgumentNullException.ThrowIfNull(waitObject);
- ArgumentNullException.ThrowIfNull(callBack);
-
Thread.ThrowIfNoThreadStart();
- RegisteredWaitHandle registeredHandle = new RegisteredWaitHandle(
- waitObject,
- new _ThreadPoolWaitOrTimerCallback(callBack, state, flowExecutionContext),
- (int)millisecondsTimeOutInterval,
- !executeOnlyOnce);
- PortableThreadPool.ThreadPoolInstance.RegisterWaitHandle(registeredHandle);
- return registeredHandle;
+ return PortableThreadPool.RegisterWaitForSingleObject(waitObject, callBack, state, millisecondsTimeOutInterval, executeOnlyOnce, flowExecutionContext);
+ }
+
+ [CLSCompliant(false)]
+ [SupportedOSPlatform("windows")]
+ public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) =>
+ throw new PlatformNotSupportedException(SR.PlatformNotSupported_OverlappedIO);
+
+ [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")]
+ [SupportedOSPlatform("windows")]
+ public static bool BindHandle(IntPtr osHandle) =>
+ throw new PlatformNotSupportedException(SR.PlatformNotSupported_OverlappedIO);
+
+ [SupportedOSPlatform("windows")]
+ public static bool BindHandle(SafeHandle osHandle) =>
+ throw new PlatformNotSupportedException(SR.PlatformNotSupported_OverlappedIO);
+
+ /// <summary>
+ /// Gets the number of thread pool threads that currently exist.
+ /// </summary>
+ /// <remarks>
+ /// For a thread pool implementation that may have different types of threads, the count includes all types.
+ /// </remarks>
+ public static int ThreadCount
+ {
+ get
+ {
+ return PortableThreadPool.ThreadPoolInstance.ThreadCount;
+ }
}
+
+ /// <summary>
+ /// Gets the number of work items that have been processed so far.
+ /// </summary>
+ /// <remarks>
+ /// For a thread pool implementation that may have different types of work items, the count includes all types.
+ /// </remarks>
+ public static long CompletedWorkItemCount
+ {
+ get
+ {
+ return PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount;
+ }
+ }
+
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace System.Threading
+{
+ public static partial class ThreadPool
+ {
+ internal static bool UseWindowsThreadPool { get; } =
+ AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.UseWindowsThreadPool", "DOTNET_ThreadPool_UseWindowsThreadPool");
+
+#if NATIVEAOT
+ private const bool IsWorkerTrackingEnabledInConfig = false;
+#else
+ private static readonly bool IsWorkerTrackingEnabledInConfig =
+ UseWindowsThreadPool ? false : AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.EnableWorkerTracking", "DOTNET_ThreadPool_EnableWorkerTracking");
+#endif
+
+ // Indicates whether the thread pool should yield the thread from the dispatch loop to the runtime periodically so that
+ // the runtime may use the thread for processing other work.
+ //
+ // Windows thread pool threads need to yield back to the thread pool periodically, otherwise those threads may be
+ // considered to be doing long-running work and change thread pool heuristics, such as slowing or halting thread
+ // injection.
+ internal static bool YieldFromDispatchLoop => UseWindowsThreadPool;
+
+ [CLSCompliant(false)]
+ [SupportedOSPlatform("windows")]
+ public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) =>
+ ThreadPool.UseWindowsThreadPool ?
+ WindowsThreadPool.UnsafeQueueNativeOverlapped(overlapped) :
+ UnsafeQueueNativeOverlappedPortableCore(overlapped);
+
+ [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")]
+ [SupportedOSPlatform("windows")]
+ public static bool BindHandle(IntPtr osHandle) =>
+ ThreadPool.UseWindowsThreadPool ?
+ WindowsThreadPool.BindHandle(osHandle) :
+ BindHandlePortableCore(osHandle);
+
+ [SupportedOSPlatform("windows")]
+ public static bool BindHandle(SafeHandle osHandle) =>
+ ThreadPool.UseWindowsThreadPool ?
+ WindowsThreadPool.BindHandle(osHandle) :
+ BindHandlePortableCore(osHandle);
+
+#if !CORECLR
+ internal static bool EnsureConfigInitialized() => true;
+#endif
+
+ internal static void InitializeForThreadPoolThread()
+ {
+ if (ThreadPool.UseWindowsThreadPool)
+ {
+ WindowsThreadPool.InitializeForThreadPoolThread();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static void IncrementCompletedWorkItemCount() => WindowsThreadPool.IncrementCompletedWorkItemCount();
+
+ internal static object GetOrCreateThreadLocalCompletionCountObject() =>
+ ThreadPool.UseWindowsThreadPool ?
+ WindowsThreadPool.GetOrCreateThreadLocalCompletionCountObject() :
+ PortableThreadPool.ThreadPoolInstance.GetOrCreateThreadLocalCompletionCountObject();
+
+ public static bool SetMaxThreads(int workerThreads, int completionPortThreads) =>
+ ThreadPool.UseWindowsThreadPool ?
+ WindowsThreadPool.SetMaxThreads(workerThreads, completionPortThreads) :
+ PortableThreadPool.ThreadPoolInstance.SetMaxThreads(workerThreads, completionPortThreads);
+
+ public static void GetMaxThreads(out int workerThreads, out int completionPortThreads)
+ {
+ if (ThreadPool.UseWindowsThreadPool)
+ {
+ WindowsThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);
+ }
+ else
+ {
+ PortableThreadPool.ThreadPoolInstance.GetMaxThreads(out workerThreads, out completionPortThreads);
+ }
+ }
+
+ public static bool SetMinThreads(int workerThreads, int completionPortThreads) =>
+ ThreadPool.UseWindowsThreadPool ?
+ WindowsThreadPool.SetMinThreads(workerThreads, completionPortThreads) :
+ PortableThreadPool.ThreadPoolInstance.SetMinThreads(workerThreads, completionPortThreads);
+
+ public static void GetMinThreads(out int workerThreads, out int completionPortThreads)
+ {
+ if (ThreadPool.UseWindowsThreadPool)
+ {
+ WindowsThreadPool.GetMinThreads(out workerThreads, out completionPortThreads);
+ }
+ else
+ {
+ PortableThreadPool.ThreadPoolInstance.GetMinThreads(out workerThreads, out completionPortThreads);
+ }
+ }
+
+ public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads)
+ {
+ if (ThreadPool.UseWindowsThreadPool)
+ {
+ WindowsThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
+ }
+ else
+ {
+ PortableThreadPool.ThreadPoolInstance.GetAvailableThreads(out workerThreads, out completionPortThreads);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static void NotifyWorkItemProgress()
+ {
+ if (ThreadPool.UseWindowsThreadPool)
+ {
+ WindowsThreadPool.NotifyWorkItemProgress();
+ }
+ else
+ {
+ PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static bool NotifyWorkItemComplete(object threadLocalCompletionCountObject, int currentTimeMs) =>
+ ThreadPool.UseWindowsThreadPool ?
+ WindowsThreadPool.NotifyWorkItemComplete(threadLocalCompletionCountObject, currentTimeMs) :
+ PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete(threadLocalCompletionCountObject, currentTimeMs);
+
+ internal static bool NotifyThreadBlocked() =>
+ ThreadPool.UseWindowsThreadPool ?
+ WindowsThreadPool.NotifyThreadBlocked() :
+ PortableThreadPool.ThreadPoolInstance.NotifyThreadBlocked();
+
+ internal static void NotifyThreadUnblocked()
+ {
+ if (ThreadPool.UseWindowsThreadPool)
+ {
+ WindowsThreadPool.NotifyThreadUnblocked();
+ }
+ else
+ {
+ PortableThreadPool.ThreadPoolInstance.NotifyThreadUnblocked();
+ }
+ }
+
+ /// <summary>
+ /// This method is called to request a new thread pool worker to handle pending work.
+ /// </summary>
+ internal static unsafe void RequestWorkerThread()
+ {
+ if (ThreadPool.UseWindowsThreadPool)
+ {
+ WindowsThreadPool.RequestWorkerThread();
+ }
+ else
+ {
+ PortableThreadPool.ThreadPoolInstance.RequestWorker();
+ }
+ }
+
+ internal static void ReportThreadStatus(bool isWorking)
+ {
+ Debug.Assert(!ThreadPool.UseWindowsThreadPool);
+ PortableThreadPool.ThreadPoolInstance.ReportThreadStatus(isWorking);
+ }
+
+ /// <summary>
+ /// Gets the number of thread pool threads that currently exist.
+ /// </summary>
+ /// <remarks>
+ /// For a thread pool implementation that may have different types of threads, the count includes all types.
+ /// </remarks>
+ public static int ThreadCount
+ {
+ get
+ {
+ return ThreadPool.UseWindowsThreadPool ? WindowsThreadPool.ThreadCount : PortableThreadPool.ThreadPoolInstance.ThreadCount;
+ }
+ }
+
+ /// <summary>
+ /// Gets the number of work items that have been processed so far.
+ /// </summary>
+ /// <remarks>
+ /// For a thread pool implementation that may have different types of work items, the count includes all types.
+ /// </remarks>
+ public static long CompletedWorkItemCount
+ {
+ get
+ {
+ return ThreadPool.UseWindowsThreadPool ? WindowsThreadPool.CompletedWorkItemCount : PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount;
+ }
+ }
+
+ private static RegisteredWaitHandle RegisterWaitForSingleObject(
+ WaitHandle waitObject,
+ WaitOrTimerCallback callBack,
+ object? state,
+ uint millisecondsTimeOutInterval,
+ bool executeOnlyOnce,
+ bool flowExecutionContext)
+ {
+ if (ThreadPool.UseWindowsThreadPool)
+ {
+ return WindowsThreadPool.RegisterWaitForSingleObject(waitObject, callBack, state, millisecondsTimeOutInterval, executeOnlyOnce, flowExecutionContext);
+ }
+ else
+ {
+ return PortableThreadPool.RegisterWaitForSingleObject(waitObject, callBack, state, millisecondsTimeOutInterval, executeOnlyOnce, flowExecutionContext);
+ }
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace System.Threading
+{
+ //
+ // Implementation of ThreadPoolBoundHandle that sits on top of the CLR's ThreadPool and Overlapped infrastructure
+ //
+
+ /// <summary>
+ /// Represents an I/O handle that is bound to the system thread pool and enables low-level
+ /// components to receive notifications for asynchronous I/O operations.
+ /// </summary>
+ public sealed partial class ThreadPoolBoundHandle : IDisposable
+ {
+ private unsafe NativeOverlapped* AllocateNativeOverlappedPortableCore(IOCompletionCallback callback, object? state, object? pinData) =>
+ AllocateNativeOverlappedPortableCore(callback, state, pinData, flowExecutionContext: true);
+
+ private unsafe NativeOverlapped* UnsafeAllocateNativeOverlappedPortableCore(IOCompletionCallback callback, object? state, object? pinData) =>
+ AllocateNativeOverlappedPortableCore(callback, state, pinData, flowExecutionContext: false);
+
+ private unsafe NativeOverlapped* AllocateNativeOverlappedPortableCore(IOCompletionCallback callback, object? state, object? pinData, bool flowExecutionContext)
+ {
+ ArgumentNullException.ThrowIfNull(callback);
+ ObjectDisposedException.ThrowIf(_isDisposed, this);
+
+ ThreadPoolBoundHandleOverlapped overlapped = new ThreadPoolBoundHandleOverlapped(callback, state, pinData, preAllocated: null, flowExecutionContext);
+ overlapped._boundHandle = this;
+ return overlapped._nativeOverlapped;
+ }
+
+ private unsafe NativeOverlapped* AllocateNativeOverlappedPortableCore(PreAllocatedOverlapped preAllocated)
+ {
+ ArgumentNullException.ThrowIfNull(preAllocated);
+ ObjectDisposedException.ThrowIf(_isDisposed, this);
+
+ preAllocated.AddRef();
+ try
+ {
+ ThreadPoolBoundHandleOverlapped overlapped = preAllocated._overlappedPortableCore!;
+
+ if (overlapped._boundHandle != null)
+ throw new ArgumentException(SR.Argument_PreAllocatedAlreadyAllocated, nameof(preAllocated));
+
+ overlapped._boundHandle = this;
+
+ return overlapped._nativeOverlapped;
+ }
+ catch
+ {
+ preAllocated.Release();
+ throw;
+ }
+ }
+
+ private unsafe void FreeNativeOverlappedPortableCore(NativeOverlapped* overlapped)
+ {
+ ArgumentNullException.ThrowIfNull(overlapped);
+
+ // Note: we explicitly allow FreeNativeOverlapped calls after the ThreadPoolBoundHandle has been Disposed.
+
+ ThreadPoolBoundHandleOverlapped wrapper = GetOverlappedWrapper(overlapped);
+
+ if (wrapper._boundHandle != this)
+ throw new ArgumentException(SR.Argument_NativeOverlappedWrongBoundHandle, nameof(overlapped));
+
+ if (wrapper._preAllocated != null)
+ wrapper._preAllocated.Release();
+ else
+ Overlapped.Free(overlapped);
+ }
+
+ private static unsafe object? GetNativeOverlappedStatePortableCore(NativeOverlapped* overlapped)
+ {
+ ArgumentNullException.ThrowIfNull(overlapped);
+
+ ThreadPoolBoundHandleOverlapped wrapper = GetOverlappedWrapper(overlapped);
+ Debug.Assert(wrapper._boundHandle != null);
+ return wrapper._userState;
+ }
+
+ private static unsafe ThreadPoolBoundHandleOverlapped GetOverlappedWrapper(NativeOverlapped* overlapped)
+ {
+ ThreadPoolBoundHandleOverlapped wrapper;
+ try
+ {
+ wrapper = (ThreadPoolBoundHandleOverlapped)Overlapped.Unpack(overlapped);
+ }
+ catch (NullReferenceException ex)
+ {
+ throw new ArgumentException(SR.Argument_NativeOverlappedAlreadyFree, nameof(overlapped), ex);
+ }
+
+ return wrapper;
+ }
+
+ private void DisposePortableCore()
+ {
+ // .NET Native's version of ThreadPoolBoundHandle that wraps the Win32 ThreadPool holds onto
+ // native resources so it needs to be disposable. To match the contract, we are also disposable.
+ // We also implement a disposable state to mimic behavior between this implementation and
+ // .NET Native's version (code written against us, will also work against .NET Native's version).
+ _isDisposed = true;
+ }
+ }
+}
namespace System.Threading
{
- public sealed partial class ThreadPoolBoundHandle
+ //
+ // Implementation of ThreadPoolBoundHandle that sits on top of the CLR's ThreadPool and Overlapped infrastructure
+ //
+
+ /// <summary>
+ /// Represents an I/O handle that is bound to the system thread pool and enables low-level
+ /// components to receive notifications for asynchronous I/O operations.
+ /// </summary>
+ public sealed partial class ThreadPoolBoundHandle : IDisposable
{
- private static ThreadPoolBoundHandle BindHandleCore(SafeHandle handle)
+ private readonly SafeHandle _handle;
+ private bool _isDisposed;
+
+ private ThreadPoolBoundHandle(SafeHandle handle)
{
- Debug.Assert(handle != null);
- Debug.Assert(!handle.IsClosed);
- Debug.Assert(!handle.IsInvalid);
+ _handle = handle;
+ }
+
+ /// <summary>
+ /// Gets the bound operating system handle.
+ /// </summary>
+ /// <value>
+ /// A <see cref="SafeHandle"/> object that holds the bound operating system handle.
+ /// </value>
+ public SafeHandle Handle => _handle;
+
+ /// <summary>
+ /// Returns a <see cref="ThreadPoolBoundHandle"/> for the specific handle,
+ /// which is bound to the system thread pool.
+ /// </summary>
+ /// <param name="handle">
+ /// A <see cref="SafeHandle"/> object that holds the operating system handle. The
+ /// handle must have been opened for overlapped I/O on the unmanaged side.
+ /// </param>
+ /// <returns>
+ /// <see cref="ThreadPoolBoundHandle"/> for <paramref name="handle"/>, which
+ /// is bound to the system thread pool.
+ /// </returns>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="handle"/> is <see langword="null"/>.
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="handle"/> has been disposed.
+ /// <para>
+ /// -or-
+ /// </para>
+ /// <paramref name="handle"/> does not refer to a valid I/O handle.
+ /// <para>
+ /// -or-
+ /// </para>
+ /// <paramref name="handle"/> refers to a handle that has not been opened
+ /// for overlapped I/O.
+ /// <para>
+ /// -or-
+ /// </para>
+ /// <paramref name="handle"/> refers to a handle that has already been bound.
+ /// </exception>
+ /// <remarks>
+ /// This method should be called once per handle.
+ /// <para>
+ /// -or-
+ /// </para>
+ /// <see cref="ThreadPoolBoundHandle"/> does not take ownership of <paramref name="handle"/>,
+ /// it remains the responsibility of the caller to call <see cref="SafeHandle.Dispose()"/>.
+ /// </remarks>
+ public static ThreadPoolBoundHandle BindHandle(SafeHandle handle)
+ {
+ ArgumentNullException.ThrowIfNull(handle);
+
+ if (handle.IsClosed || handle.IsInvalid)
+ throw new ArgumentException(SR.Argument_InvalidHandle, nameof(handle));
throw new PlatformNotSupportedException(SR.PlatformNotSupported_OverlappedIO);
}
+
+ /// <summary>
+ /// Returns an unmanaged pointer to a <see cref="NativeOverlapped"/> structure, specifying
+ /// a delegate that is invoked when the asynchronous I/O operation is complete, a user-provided
+ /// object providing context, and managed objects that serve as buffers.
+ /// </summary>
+ /// <param name="callback">
+ /// An <see cref="IOCompletionCallback"/> delegate that represents the callback method
+ /// invoked when the asynchronous I/O operation completes.
+ /// </param>
+ /// <param name="state">
+ /// A user-provided object that distinguishes this <see cref="NativeOverlapped"/> from other
+ /// <see cref="NativeOverlapped"/> instances. Can be <see langword="null"/>.
+ /// </param>
+ /// <param name="pinData">
+ /// An object or array of objects representing the input or output buffer for the operation. Each
+ /// object represents a buffer, for example an array of bytes. Can be <see langword="null"/>.
+ /// </param>
+ /// <returns>
+ /// An unmanaged pointer to a <see cref="NativeOverlapped"/> structure.
+ /// </returns>
+ /// <remarks>
+ /// <para>
+ /// The unmanaged pointer returned by this method can be passed to the operating system in
+ /// overlapped I/O operations. The <see cref="NativeOverlapped"/> structure is fixed in
+ /// physical memory until <see cref="FreeNativeOverlapped(NativeOverlapped*)"/> is called.
+ /// </para>
+ /// <para>
+ /// The buffer or buffers specified in <paramref name="pinData"/> must be the same as those passed
+ /// to the unmanaged operating system function that performs the asynchronous I/O.
+ /// </para>
+ /// <note>
+ /// The buffers specified in <paramref name="pinData"/> are pinned for the duration of
+ /// the I/O operation.
+ /// </note>
+ /// </remarks>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="callback"/> is <see langword="null"/>.
+ /// </exception>
+ /// <exception cref="ObjectDisposedException">
+ /// This method was called after the <see cref="ThreadPoolBoundHandle"/> was disposed.
+ /// </exception>
+ [CLSCompliant(false)]
+ public unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) =>
+ AllocateNativeOverlappedPortableCore(callback, state, pinData);
+
+ /// <summary>
+ /// Returns an unmanaged pointer to a <see cref="NativeOverlapped"/> structure, specifying
+ /// a delegate that is invoked when the asynchronous I/O operation is complete, a user-provided
+ /// object providing context, and managed objects that serve as buffers.
+ /// </summary>
+ /// <param name="callback">
+ /// An <see cref="IOCompletionCallback"/> delegate that represents the callback method
+ /// invoked when the asynchronous I/O operation completes.
+ /// </param>
+ /// <param name="state">
+ /// A user-provided object that distinguishes this <see cref="NativeOverlapped"/> from other
+ /// <see cref="NativeOverlapped"/> instances. Can be <see langword="null"/>.
+ /// </param>
+ /// <param name="pinData">
+ /// An object or array of objects representing the input or output buffer for the operation. Each
+ /// object represents a buffer, for example an array of bytes. Can be <see langword="null"/>.
+ /// </param>
+ /// <returns>
+ /// An unmanaged pointer to a <see cref="NativeOverlapped"/> structure.
+ /// </returns>
+ /// <remarks>
+ /// <para>
+ /// The unmanaged pointer returned by this method can be passed to the operating system in
+ /// overlapped I/O operations. The <see cref="NativeOverlapped"/> structure is fixed in
+ /// physical memory until <see cref="FreeNativeOverlapped(NativeOverlapped*)"/> is called.
+ /// </para>
+ /// <para>
+ /// The buffer or buffers specified in <paramref name="pinData"/> must be the same as those passed
+ /// to the unmanaged operating system function that performs the asynchronous I/O.
+ /// </para>
+ /// <para>
+ /// <see cref="ExecutionContext"/> is not flowed to the invocation of the callback.
+ /// </para>
+ /// <note>
+ /// The buffers specified in <paramref name="pinData"/> are pinned for the duration of
+ /// the I/O operation.
+ /// </note>
+ /// </remarks>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="callback"/> is <see langword="null"/>.
+ /// </exception>
+ /// <exception cref="ObjectDisposedException">
+ /// This method was called after the <see cref="ThreadPoolBoundHandle"/> was disposed.
+ /// </exception>
+ [CLSCompliant(false)]
+ public unsafe NativeOverlapped* UnsafeAllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) =>
+ UnsafeAllocateNativeOverlappedPortableCore(callback, state, pinData);
+
+ /// <summary>
+ /// Returns an unmanaged pointer to a <see cref="NativeOverlapped"/> structure, using the callback,
+ /// state, and buffers associated with the specified <see cref="PreAllocatedOverlapped"/> object.
+ /// </summary>
+ /// <param name="preAllocated">
+ /// A <see cref="PreAllocatedOverlapped"/> object from which to create the NativeOverlapped pointer.
+ /// </param>
+ /// <returns>
+ /// An unmanaged pointer to a <see cref="NativeOverlapped"/> structure.
+ /// </returns>
+ /// <remarks>
+ /// <para>
+ /// The unmanaged pointer returned by this method can be passed to the operating system in
+ /// overlapped I/O operations. The <see cref="NativeOverlapped"/> structure is fixed in
+ /// physical memory until <see cref="FreeNativeOverlapped(NativeOverlapped*)"/> is called.
+ /// </para>
+ /// </remarks>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="preAllocated"/> is <see langword="null"/>.
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="preAllocated"/> is currently in use for another I/O operation.
+ /// </exception>
+ /// <exception cref="ObjectDisposedException">
+ /// This method was called after the <see cref="ThreadPoolBoundHandle"/> was disposed, or
+ /// this method was called after <paramref name="preAllocated"/> was disposed.
+ /// </exception>
+ /// <seealso cref="PreAllocatedOverlapped"/>
+ [CLSCompliant(false)]
+ public unsafe NativeOverlapped* AllocateNativeOverlapped(PreAllocatedOverlapped preAllocated) => AllocateNativeOverlappedPortableCore(preAllocated);
+
+ /// <summary>
+ /// Frees the unmanaged memory associated with a <see cref="NativeOverlapped"/> structure
+ /// allocated by the <see cref="AllocateNativeOverlapped"/> method.
+ /// </summary>
+ /// <param name="overlapped">
+ /// An unmanaged pointer to the <see cref="NativeOverlapped"/> structure to be freed.
+ /// </param>
+ /// <remarks>
+ /// <note type="caution">
+ /// You must call the <see cref="FreeNativeOverlapped(NativeOverlapped*)"/> method exactly once
+ /// on every <see cref="NativeOverlapped"/> unmanaged pointer allocated using the
+ /// <see cref="AllocateNativeOverlapped"/> method.
+ /// If you do not call the <see cref="FreeNativeOverlapped(NativeOverlapped*)"/> method, you will
+ /// leak memory. If you call the <see cref="FreeNativeOverlapped(NativeOverlapped*)"/> method more
+ /// than once on the same <see cref="NativeOverlapped"/> unmanaged pointer, memory will be corrupted.
+ /// </note>
+ /// </remarks>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="overlapped"/> is <see langword="null"/>.
+ /// </exception>
+ /// <exception cref="ObjectDisposedException">
+ /// This method was called after the <see cref="ThreadPoolBoundHandle"/> was disposed.
+ /// </exception>
+ [CLSCompliant(false)]
+ public unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped) => FreeNativeOverlappedPortableCore(overlapped);
+
+ /// <summary>
+ /// Returns the user-provided object specified when the <see cref="NativeOverlapped"/> instance was
+ /// allocated using the <see cref="AllocateNativeOverlapped(IOCompletionCallback, object, object)"/>.
+ /// </summary>
+ /// <param name="overlapped">
+ /// An unmanaged pointer to the <see cref="NativeOverlapped"/> structure from which to return the
+ /// associated user-provided object.
+ /// </param>
+ /// <returns>
+ /// A user-provided object that distinguishes this <see cref="NativeOverlapped"/>
+ /// from other <see cref="NativeOverlapped"/> instances, otherwise, <see langword="null"/> if one was
+ /// not specified when the instance was allocated using <see cref="AllocateNativeOverlapped"/>.
+ /// </returns>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="overlapped"/> is <see langword="null"/>.
+ /// </exception>
+ [CLSCompliant(false)]
+ public static unsafe object? GetNativeOverlappedState(NativeOverlapped* overlapped) => GetNativeOverlappedStatePortableCore(overlapped);
+
+ public void Dispose() => DisposePortableCore();
}
}
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
+using System.IO;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Threading;
+using Microsoft.Win32.SafeHandles;
namespace System.Threading
{
- public sealed partial class ThreadPoolBoundHandle
+ /// <summary>
+ /// Represents an I/O handle that is bound to the system thread pool and enables low-level
+ /// components to receive notifications for asynchronous I/O operations.
+ /// </summary>
+ public sealed partial class ThreadPoolBoundHandle : IDisposable, IDeferredDisposable
{
+ private readonly SafeHandle _handle;
+ private readonly SafeThreadPoolIOHandle? _threadPoolHandle;
+ private DeferredDisposableLifetime<ThreadPoolBoundHandle> _lifetime;
+ private bool _isDisposed;
+
+ private ThreadPoolBoundHandle(SafeHandle handle, SafeThreadPoolIOHandle threadPoolHandle)
+ {
+ _threadPoolHandle = threadPoolHandle;
+ _handle = handle;
+ }
+
+ private ThreadPoolBoundHandle(SafeHandle handle)
+ {
+ Debug.Assert(!ThreadPool.UseWindowsThreadPool);
+
+ _handle = handle;
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Gets the bound operating system handle.
+ /// </summary>
+ /// <value>
+ /// A <see cref="SafeHandle"/> object that holds the bound operating system handle.
+ /// </value>
+ public SafeHandle Handle => _handle;
+
private static ThreadPoolBoundHandle BindHandleCore(SafeHandle handle)
{
- Debug.Assert(handle != null);
- Debug.Assert(!handle.IsClosed);
- Debug.Assert(!handle.IsInvalid);
+ ArgumentNullException.ThrowIfNull(handle);
+
+ if (handle.IsClosed || handle.IsInvalid)
+ throw new ArgumentException(SR.Argument_InvalidHandle, nameof(handle));
try
{
return new ThreadPoolBoundHandle(handle);
}
+
+ /// <summary>
+ /// Returns a <see cref="ThreadPoolBoundHandle"/> for the specific handle,
+ /// which is bound to the system thread pool.
+ /// </summary>
+ /// <param name="handle">
+ /// A <see cref="SafeHandle"/> object that holds the operating system handle. The
+ /// handle must have been opened for overlapped I/O on the unmanaged side.
+ /// </param>
+ /// <returns>
+ /// <see cref="ThreadPoolBoundHandle"/> for <paramref name="handle"/>, which
+ /// is bound to the system thread pool.
+ /// </returns>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="handle"/> is <see langword="null"/>.
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="handle"/> has been disposed.
+ /// <para>
+ /// -or-
+ /// </para>
+ /// <paramref name="handle"/> does not refer to a valid I/O handle.
+ /// <para>
+ /// -or-
+ /// </para>
+ /// <paramref name="handle"/> refers to a handle that has not been opened
+ /// for overlapped I/O.
+ /// <para>
+ /// -or-
+ /// </para>
+ /// <paramref name="handle"/> refers to a handle that has already been bound.
+ /// </exception>
+ /// <remarks>
+ /// This method should be called once per handle.
+ /// <para>
+ /// -or-
+ /// </para>
+ /// <see cref="ThreadPoolBoundHandle"/> does not take ownership of <paramref name="handle"/>,
+ /// it remains the responsibility of the caller to call <see cref="SafeHandle.Dispose()"/>.
+ /// </remarks>
+ public static unsafe ThreadPoolBoundHandle BindHandle(SafeHandle handle) =>
+ ThreadPool.UseWindowsThreadPool ? BindHandleWindowsThreadPool(handle) : BindHandleCore(handle);
+
+ /// <summary>
+ /// Returns an unmanaged pointer to a <see cref="NativeOverlapped"/> structure, specifying
+ /// a delegate that is invoked when the asynchronous I/O operation is complete, a user-provided
+ /// object providing context, and managed objects that serve as buffers.
+ /// </summary>
+ /// <param name="callback">
+ /// An <see cref="IOCompletionCallback"/> delegate that represents the callback method
+ /// invoked when the asynchronous I/O operation completes.
+ /// </param>
+ /// <param name="state">
+ /// A user-provided object that distinguishes this <see cref="NativeOverlapped"/> from other
+ /// <see cref="NativeOverlapped"/> instances. Can be <see langword="null"/>.
+ /// </param>
+ /// <param name="pinData">
+ /// An object or array of objects representing the input or output buffer for the operation. Each
+ /// object represents a buffer, for example an array of bytes. Can be <see langword="null"/>.
+ /// </param>
+ /// <returns>
+ /// An unmanaged pointer to a <see cref="NativeOverlapped"/> structure.
+ /// </returns>
+ /// <remarks>
+ /// <para>
+ /// The unmanaged pointer returned by this method can be passed to the operating system in
+ /// overlapped I/O operations. The <see cref="NativeOverlapped"/> structure is fixed in
+ /// physical memory until <see cref="FreeNativeOverlapped(NativeOverlapped*)"/> is called.
+ /// </para>
+ /// <para>
+ /// The buffer or buffers specified in <paramref name="pinData"/> must be the same as those passed
+ /// to the unmanaged operating system function that performs the asynchronous I/O.
+ /// </para>
+ /// <note>
+ /// The buffers specified in <paramref name="pinData"/> are pinned for the duration of
+ /// the I/O operation.
+ /// </note>
+ /// </remarks>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="callback"/> is <see langword="null"/>.
+ /// </exception>
+ /// <exception cref="ObjectDisposedException">
+ /// This method was called after the <see cref="ThreadPoolBoundHandle"/> was disposed.
+ /// </exception>
+ [CLSCompliant(false)]
+ public unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) =>
+ ThreadPool.UseWindowsThreadPool ?
+ AllocateNativeOverlappedWindowsThreadPool(callback, state, pinData) :
+ AllocateNativeOverlappedPortableCore(callback, state, pinData);
+
+ /// <summary>
+ /// Returns an unmanaged pointer to a <see cref="NativeOverlapped"/> structure, specifying
+ /// a delegate that is invoked when the asynchronous I/O operation is complete, a user-provided
+ /// object providing context, and managed objects that serve as buffers.
+ /// </summary>
+ /// <param name="callback">
+ /// An <see cref="IOCompletionCallback"/> delegate that represents the callback method
+ /// invoked when the asynchronous I/O operation completes.
+ /// </param>
+ /// <param name="state">
+ /// A user-provided object that distinguishes this <see cref="NativeOverlapped"/> from other
+ /// <see cref="NativeOverlapped"/> instances. Can be <see langword="null"/>.
+ /// </param>
+ /// <param name="pinData">
+ /// An object or array of objects representing the input or output buffer for the operation. Each
+ /// object represents a buffer, for example an array of bytes. Can be <see langword="null"/>.
+ /// </param>
+ /// <returns>
+ /// An unmanaged pointer to a <see cref="NativeOverlapped"/> structure.
+ /// </returns>
+ /// <remarks>
+ /// <para>
+ /// The unmanaged pointer returned by this method can be passed to the operating system in
+ /// overlapped I/O operations. The <see cref="NativeOverlapped"/> structure is fixed in
+ /// physical memory until <see cref="FreeNativeOverlapped(NativeOverlapped*)"/> is called.
+ /// </para>
+ /// <para>
+ /// The buffer or buffers specified in <paramref name="pinData"/> must be the same as those passed
+ /// to the unmanaged operating system function that performs the asynchronous I/O.
+ /// </para>
+ /// <para>
+ /// <see cref="ExecutionContext"/> is not flowed to the invocation of the callback.
+ /// </para>
+ /// <note>
+ /// The buffers specified in <paramref name="pinData"/> are pinned for the duration of
+ /// the I/O operation.
+ /// </note>
+ /// </remarks>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="callback"/> is <see langword="null"/>.
+ /// </exception>
+ /// <exception cref="ObjectDisposedException">
+ /// This method was called after the <see cref="ThreadPoolBoundHandle"/> was disposed.
+ /// </exception>
+ [CLSCompliant(false)]
+ public unsafe NativeOverlapped* UnsafeAllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) =>
+ ThreadPool.UseWindowsThreadPool ?
+ UnsafeAllocateNativeOverlappedWindowsThreadPool(callback, state, pinData) :
+ UnsafeAllocateNativeOverlappedPortableCore(callback, state, pinData);
+
+ /// <summary>
+ /// Returns an unmanaged pointer to a <see cref="NativeOverlapped"/> structure, using the callback,
+ /// state, and buffers associated with the specified <see cref="PreAllocatedOverlapped"/> object.
+ /// </summary>
+ /// <param name="preAllocated">
+ /// A <see cref="PreAllocatedOverlapped"/> object from which to create the NativeOverlapped pointer.
+ /// </param>
+ /// <returns>
+ /// An unmanaged pointer to a <see cref="NativeOverlapped"/> structure.
+ /// </returns>
+ /// <remarks>
+ /// <para>
+ /// The unmanaged pointer returned by this method can be passed to the operating system in
+ /// overlapped I/O operations. The <see cref="NativeOverlapped"/> structure is fixed in
+ /// physical memory until <see cref="FreeNativeOverlapped(NativeOverlapped*)"/> is called.
+ /// </para>
+ /// </remarks>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="preAllocated"/> is <see langword="null"/>.
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="preAllocated"/> is currently in use for another I/O operation.
+ /// </exception>
+ /// <exception cref="ObjectDisposedException">
+ /// This method was called after the <see cref="ThreadPoolBoundHandle"/> was disposed, or
+ /// this method was called after <paramref name="preAllocated"/> was disposed.
+ /// </exception>
+ /// <seealso cref="PreAllocatedOverlapped"/>
+ [CLSCompliant(false)]
+ public unsafe NativeOverlapped* AllocateNativeOverlapped(PreAllocatedOverlapped preAllocated) =>
+ ThreadPool.UseWindowsThreadPool ?
+ AllocateNativeOverlappedWindowsThreadPool(preAllocated) :
+ AllocateNativeOverlappedPortableCore(preAllocated);
+
+ /// <summary>
+ /// Frees the unmanaged memory associated with a <see cref="NativeOverlapped"/> structure
+ /// allocated by the <see cref="AllocateNativeOverlapped"/> method.
+ /// </summary>
+ /// <param name="overlapped">
+ /// An unmanaged pointer to the <see cref="NativeOverlapped"/> structure to be freed.
+ /// </param>
+ /// <remarks>
+ /// <note type="caution">
+ /// You must call the <see cref="FreeNativeOverlapped(NativeOverlapped*)"/> method exactly once
+ /// on every <see cref="NativeOverlapped"/> unmanaged pointer allocated using the
+ /// <see cref="AllocateNativeOverlapped"/> method.
+ /// If you do not call the <see cref="FreeNativeOverlapped(NativeOverlapped*)"/> method, you will
+ /// leak memory. If you call the <see cref="FreeNativeOverlapped(NativeOverlapped*)"/> method more
+ /// than once on the same <see cref="NativeOverlapped"/> unmanaged pointer, memory will be corrupted.
+ /// </note>
+ /// </remarks>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="overlapped"/> is <see langword="null"/>.
+ /// </exception>
+ /// <exception cref="ObjectDisposedException">
+ /// This method was called after the <see cref="ThreadPoolBoundHandle"/> was disposed.
+ /// </exception>
+ [CLSCompliant(false)]
+ public unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped)
+ {
+ if (ThreadPool.UseWindowsThreadPool)
+ {
+ FreeNativeOverlappedWindowsThreadPool(overlapped);
+ }
+ else
+ {
+ FreeNativeOverlappedPortableCore(overlapped);
+ }
+ }
+
+ /// <summary>
+ /// Returns the user-provided object specified when the <see cref="NativeOverlapped"/> instance was
+ /// allocated using the <see cref="AllocateNativeOverlapped(IOCompletionCallback, object, object)"/>.
+ /// </summary>
+ /// <param name="overlapped">
+ /// An unmanaged pointer to the <see cref="NativeOverlapped"/> structure from which to return the
+ /// associated user-provided object.
+ /// </param>
+ /// <returns>
+ /// A user-provided object that distinguishes this <see cref="NativeOverlapped"/>
+ /// from other <see cref="NativeOverlapped"/> instances, otherwise, <see langword="null"/> if one was
+ /// not specified when the instance was allocated using <see cref="AllocateNativeOverlapped"/>.
+ /// </returns>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="overlapped"/> is <see langword="null"/>.
+ /// </exception>
+ [CLSCompliant(false)]
+ public static unsafe object? GetNativeOverlappedState(NativeOverlapped* overlapped) =>
+ ThreadPool.UseWindowsThreadPool ?
+ GetNativeOverlappedStateWindowsThreadPool(overlapped) :
+ GetNativeOverlappedStatePortableCore(overlapped);
+
+ public void Dispose()
+ {
+ if (ThreadPool.UseWindowsThreadPool)
+ {
+ DisposeWindowsThreadPool();
+ }
+ else
+ {
+ DisposePortableCore();
+ }
+ }
+
+ ~ThreadPoolBoundHandle()
+ {
+ Debug.Assert(ThreadPool.UseWindowsThreadPool);
+ FinalizeWindowsThreadPool();
+ }
+
+ void IDeferredDisposable.OnFinalRelease(bool disposed)
+ {
+ if (ThreadPool.UseWindowsThreadPool)
+ {
+ IDeferredDisposableOnFinalReleaseWindowsThreadPool(disposed);
+ }
+ }
}
}
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Win32.SafeHandles;
//
// Implementation of ThreadPoolBoundHandle that sits on top of the Win32 ThreadPool
//
- public sealed class ThreadPoolBoundHandle : IDisposable, IDeferredDisposable
+ public sealed partial class ThreadPoolBoundHandle : IDisposable, IDeferredDisposable
{
- private readonly SafeHandle _handle;
- private readonly SafeThreadPoolIOHandle _threadPoolHandle;
- private DeferredDisposableLifetime<ThreadPoolBoundHandle> _lifetime;
-
- private ThreadPoolBoundHandle(SafeHandle handle, SafeThreadPoolIOHandle threadPoolHandle)
- {
- _threadPoolHandle = threadPoolHandle;
- _handle = handle;
- }
-
- public SafeHandle Handle
- {
- get { return _handle; }
- }
-
- public static unsafe ThreadPoolBoundHandle BindHandle(SafeHandle handle)
+ private static unsafe ThreadPoolBoundHandle BindHandleWindowsThreadPool(SafeHandle handle)
{
ArgumentNullException.ThrowIfNull(handle);
return new ThreadPoolBoundHandle(handle, threadPoolHandle);
}
- [CLSCompliant(false)]
- public unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) =>
- AllocateNativeOverlapped(callback, state, pinData, flowExecutionContext: true);
+ private unsafe NativeOverlapped* AllocateNativeOverlappedWindowsThreadPool(IOCompletionCallback callback, object? state, object? pinData) =>
+ AllocateNativeOverlappedWindowsThreadPool(callback, state, pinData, flowExecutionContext: true);
- [CLSCompliant(false)]
- public unsafe NativeOverlapped* UnsafeAllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) =>
- AllocateNativeOverlapped(callback, state, pinData, flowExecutionContext: false);
+ private unsafe NativeOverlapped* UnsafeAllocateNativeOverlappedWindowsThreadPool(IOCompletionCallback callback, object? state, object? pinData) =>
+ AllocateNativeOverlappedWindowsThreadPool(callback, state, pinData, flowExecutionContext: false);
- private unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object state, object pinData, bool flowExecutionContext)
+ private unsafe NativeOverlapped* AllocateNativeOverlappedWindowsThreadPool(IOCompletionCallback callback, object? state, object? pinData, bool flowExecutionContext)
{
ArgumentNullException.ThrowIfNull(callback);
Win32ThreadPoolNativeOverlapped* overlapped = Win32ThreadPoolNativeOverlapped.Allocate(callback, state, pinData, preAllocated: null, flowExecutionContext);
overlapped->Data._boundHandle = this;
- Interop.Kernel32.StartThreadpoolIo(_threadPoolHandle);
+ Interop.Kernel32.StartThreadpoolIo(_threadPoolHandle!);
return Win32ThreadPoolNativeOverlapped.ToNativeOverlapped(overlapped);
}
}
}
- [CLSCompliant(false)]
- public unsafe NativeOverlapped* AllocateNativeOverlapped(PreAllocatedOverlapped preAllocated)
+ private unsafe NativeOverlapped* AllocateNativeOverlappedWindowsThreadPool(PreAllocatedOverlapped preAllocated)
{
ArgumentNullException.ThrowIfNull(preAllocated);
addedRefToThis = AddRef();
addedRefToPreAllocated = preAllocated.AddRef();
- Win32ThreadPoolNativeOverlapped.OverlappedData data = preAllocated._overlapped->Data;
+ Win32ThreadPoolNativeOverlapped.OverlappedData data = preAllocated._overlappedWindowsThreadPool->Data;
if (data._boundHandle != null)
throw new ArgumentException(SR.Argument_PreAllocatedAlreadyAllocated, nameof(preAllocated));
data._boundHandle = this;
- Interop.Kernel32.StartThreadpoolIo(_threadPoolHandle);
+ Interop.Kernel32.StartThreadpoolIo(_threadPoolHandle!);
- return Win32ThreadPoolNativeOverlapped.ToNativeOverlapped(preAllocated._overlapped);
+ return Win32ThreadPoolNativeOverlapped.ToNativeOverlapped(preAllocated._overlappedWindowsThreadPool);
}
catch
{
}
}
- [CLSCompliant(false)]
- public unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped)
+ private unsafe void FreeNativeOverlappedWindowsThreadPool(NativeOverlapped* overlapped)
{
ArgumentNullException.ThrowIfNull(overlapped);
if (!data._completed)
{
- Interop.Kernel32.CancelThreadpoolIo(_threadPoolHandle);
+ Interop.Kernel32.CancelThreadpoolIo(_threadPoolHandle!);
Release();
}
Win32ThreadPoolNativeOverlapped.Free(threadPoolOverlapped);
}
- [CLSCompliant(false)]
- public static unsafe object GetNativeOverlappedState(NativeOverlapped* overlapped)
+ private static unsafe object? GetNativeOverlappedStateWindowsThreadPool(NativeOverlapped* overlapped)
{
ArgumentNullException.ThrowIfNull(overlapped);
return data._state;
}
- private static unsafe Win32ThreadPoolNativeOverlapped.OverlappedData GetOverlappedData(Win32ThreadPoolNativeOverlapped* overlapped, ThreadPoolBoundHandle expectedBoundHandle)
+ private static unsafe Win32ThreadPoolNativeOverlapped.OverlappedData GetOverlappedData(Win32ThreadPoolNativeOverlapped* overlapped, ThreadPoolBoundHandle? expectedBoundHandle)
{
Win32ThreadPoolNativeOverlapped.OverlappedData data = overlapped->Data;
private static unsafe void OnNativeIOCompleted(IntPtr instance, IntPtr context, IntPtr overlappedPtr, uint ioResult, nuint numberOfBytesTransferred, IntPtr ioPtr)
{
var wrapper = ThreadPoolCallbackWrapper.Enter();
+
Win32ThreadPoolNativeOverlapped* overlapped = (Win32ThreadPoolNativeOverlapped*)overlappedPtr;
- ThreadPoolBoundHandle boundHandle = overlapped->Data._boundHandle;
+ ThreadPoolBoundHandle? boundHandle = overlapped->Data._boundHandle;
if (boundHandle == null)
throw new InvalidOperationException(SR.Argument_NativeOverlappedAlreadyFree);
Win32ThreadPoolNativeOverlapped.CompleteWithCallback(ioResult, (uint)numberOfBytesTransferred, overlapped);
ThreadPool.IncrementCompletedWorkItemCount();
+
wrapper.Exit();
}
_lifetime.Release(this);
}
- public void Dispose()
+ private void DisposeWindowsThreadPool()
{
_lifetime.Dispose(this);
GC.SuppressFinalize(this);
}
- ~ThreadPoolBoundHandle()
+ private void FinalizeWindowsThreadPool()
{
//
// During shutdown, don't automatically clean up, because this instance may still be
Dispose();
}
- void IDeferredDisposable.OnFinalRelease(bool disposed)
+ private void IDeferredDisposableOnFinalReleaseWindowsThreadPool(bool disposed)
{
if (disposed)
- _threadPoolHandle.Dispose();
+ _threadPoolHandle!.Dispose();
}
}
}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-
-namespace System.Threading
-{
- //
- // Implementation of ThreadPoolBoundHandle that sits on top of the CLR's ThreadPool and Overlapped infrastructure
- //
-
- /// <summary>
- /// Represents an I/O handle that is bound to the system thread pool and enables low-level
- /// components to receive notifications for asynchronous I/O operations.
- /// </summary>
- public sealed partial class ThreadPoolBoundHandle : IDisposable
- {
- private readonly SafeHandle _handle;
- private bool _isDisposed;
-
- private ThreadPoolBoundHandle(SafeHandle handle)
- {
- _handle = handle;
- }
-
- /// <summary>
- /// Gets the bound operating system handle.
- /// </summary>
- /// <value>
- /// A <see cref="SafeHandle"/> object that holds the bound operating system handle.
- /// </value>
- public SafeHandle Handle => _handle;
-
- /// <summary>
- /// Returns a <see cref="ThreadPoolBoundHandle"/> for the specific handle,
- /// which is bound to the system thread pool.
- /// </summary>
- /// <param name="handle">
- /// A <see cref="SafeHandle"/> object that holds the operating system handle. The
- /// handle must have been opened for overlapped I/O on the unmanaged side.
- /// </param>
- /// <returns>
- /// <see cref="ThreadPoolBoundHandle"/> for <paramref name="handle"/>, which
- /// is bound to the system thread pool.
- /// </returns>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="handle"/> is <see langword="null"/>.
- /// </exception>
- /// <exception cref="ArgumentException">
- /// <paramref name="handle"/> has been disposed.
- /// <para>
- /// -or-
- /// </para>
- /// <paramref name="handle"/> does not refer to a valid I/O handle.
- /// <para>
- /// -or-
- /// </para>
- /// <paramref name="handle"/> refers to a handle that has not been opened
- /// for overlapped I/O.
- /// <para>
- /// -or-
- /// </para>
- /// <paramref name="handle"/> refers to a handle that has already been bound.
- /// </exception>
- /// <remarks>
- /// This method should be called once per handle.
- /// <para>
- /// -or-
- /// </para>
- /// <see cref="ThreadPoolBoundHandle"/> does not take ownership of <paramref name="handle"/>,
- /// it remains the responsibility of the caller to call <see cref="SafeHandle.Dispose()"/>.
- /// </remarks>
- public static ThreadPoolBoundHandle BindHandle(SafeHandle handle)
- {
- ArgumentNullException.ThrowIfNull(handle);
-
- if (handle.IsClosed || handle.IsInvalid)
- throw new ArgumentException(SR.Argument_InvalidHandle, nameof(handle));
-
- return BindHandleCore(handle);
- }
-
- /// <summary>
- /// Returns an unmanaged pointer to a <see cref="NativeOverlapped"/> structure, specifying
- /// a delegate that is invoked when the asynchronous I/O operation is complete, a user-provided
- /// object providing context, and managed objects that serve as buffers.
- /// </summary>
- /// <param name="callback">
- /// An <see cref="IOCompletionCallback"/> delegate that represents the callback method
- /// invoked when the asynchronous I/O operation completes.
- /// </param>
- /// <param name="state">
- /// A user-provided object that distinguishes this <see cref="NativeOverlapped"/> from other
- /// <see cref="NativeOverlapped"/> instances. Can be <see langword="null"/>.
- /// </param>
- /// <param name="pinData">
- /// An object or array of objects representing the input or output buffer for the operation. Each
- /// object represents a buffer, for example an array of bytes. Can be <see langword="null"/>.
- /// </param>
- /// <returns>
- /// An unmanaged pointer to a <see cref="NativeOverlapped"/> structure.
- /// </returns>
- /// <remarks>
- /// <para>
- /// The unmanaged pointer returned by this method can be passed to the operating system in
- /// overlapped I/O operations. The <see cref="NativeOverlapped"/> structure is fixed in
- /// physical memory until <see cref="FreeNativeOverlapped(NativeOverlapped*)"/> is called.
- /// </para>
- /// <para>
- /// The buffer or buffers specified in <paramref name="pinData"/> must be the same as those passed
- /// to the unmanaged operating system function that performs the asynchronous I/O.
- /// </para>
- /// <note>
- /// The buffers specified in <paramref name="pinData"/> are pinned for the duration of
- /// the I/O operation.
- /// </note>
- /// </remarks>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="callback"/> is <see langword="null"/>.
- /// </exception>
- /// <exception cref="ObjectDisposedException">
- /// This method was called after the <see cref="ThreadPoolBoundHandle"/> was disposed.
- /// </exception>
- [CLSCompliant(false)]
- public unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) =>
- AllocateNativeOverlapped(callback, state, pinData, flowExecutionContext: true);
-
- /// <summary>
- /// Returns an unmanaged pointer to a <see cref="NativeOverlapped"/> structure, specifying
- /// a delegate that is invoked when the asynchronous I/O operation is complete, a user-provided
- /// object providing context, and managed objects that serve as buffers.
- /// </summary>
- /// <param name="callback">
- /// An <see cref="IOCompletionCallback"/> delegate that represents the callback method
- /// invoked when the asynchronous I/O operation completes.
- /// </param>
- /// <param name="state">
- /// A user-provided object that distinguishes this <see cref="NativeOverlapped"/> from other
- /// <see cref="NativeOverlapped"/> instances. Can be <see langword="null"/>.
- /// </param>
- /// <param name="pinData">
- /// An object or array of objects representing the input or output buffer for the operation. Each
- /// object represents a buffer, for example an array of bytes. Can be <see langword="null"/>.
- /// </param>
- /// <returns>
- /// An unmanaged pointer to a <see cref="NativeOverlapped"/> structure.
- /// </returns>
- /// <remarks>
- /// <para>
- /// The unmanaged pointer returned by this method can be passed to the operating system in
- /// overlapped I/O operations. The <see cref="NativeOverlapped"/> structure is fixed in
- /// physical memory until <see cref="FreeNativeOverlapped(NativeOverlapped*)"/> is called.
- /// </para>
- /// <para>
- /// The buffer or buffers specified in <paramref name="pinData"/> must be the same as those passed
- /// to the unmanaged operating system function that performs the asynchronous I/O.
- /// </para>
- /// <para>
- /// <see cref="ExecutionContext"/> is not flowed to the invocation of the callback.
- /// </para>
- /// <note>
- /// The buffers specified in <paramref name="pinData"/> are pinned for the duration of
- /// the I/O operation.
- /// </note>
- /// </remarks>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="callback"/> is <see langword="null"/>.
- /// </exception>
- /// <exception cref="ObjectDisposedException">
- /// This method was called after the <see cref="ThreadPoolBoundHandle"/> was disposed.
- /// </exception>
- [CLSCompliant(false)]
- public unsafe NativeOverlapped* UnsafeAllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) =>
- AllocateNativeOverlapped(callback, state, pinData, flowExecutionContext: false);
-
- private unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData, bool flowExecutionContext)
- {
- ArgumentNullException.ThrowIfNull(callback);
- ObjectDisposedException.ThrowIf(_isDisposed, this);
-
- ThreadPoolBoundHandleOverlapped overlapped = new ThreadPoolBoundHandleOverlapped(callback, state, pinData, preAllocated: null, flowExecutionContext);
- overlapped._boundHandle = this;
- return overlapped._nativeOverlapped;
- }
-
- /// <summary>
- /// Returns an unmanaged pointer to a <see cref="NativeOverlapped"/> structure, using the callback,
- /// state, and buffers associated with the specified <see cref="PreAllocatedOverlapped"/> object.
- /// </summary>
- /// <param name="preAllocated">
- /// A <see cref="PreAllocatedOverlapped"/> object from which to create the NativeOverlapped pointer.
- /// </param>
- /// <returns>
- /// An unmanaged pointer to a <see cref="NativeOverlapped"/> structure.
- /// </returns>
- /// <remarks>
- /// <para>
- /// The unmanaged pointer returned by this method can be passed to the operating system in
- /// overlapped I/O operations. The <see cref="NativeOverlapped"/> structure is fixed in
- /// physical memory until <see cref="FreeNativeOverlapped(NativeOverlapped*)"/> is called.
- /// </para>
- /// </remarks>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="preAllocated"/> is <see langword="null"/>.
- /// </exception>
- /// <exception cref="ArgumentException">
- /// <paramref name="preAllocated"/> is currently in use for another I/O operation.
- /// </exception>
- /// <exception cref="ObjectDisposedException">
- /// This method was called after the <see cref="ThreadPoolBoundHandle"/> was disposed, or
- /// this method was called after <paramref name="preAllocated"/> was disposed.
- /// </exception>
- /// <seealso cref="PreAllocatedOverlapped"/>
- [CLSCompliant(false)]
- public unsafe NativeOverlapped* AllocateNativeOverlapped(PreAllocatedOverlapped preAllocated)
- {
- ArgumentNullException.ThrowIfNull(preAllocated);
- ObjectDisposedException.ThrowIf(_isDisposed, this);
-
- preAllocated.AddRef();
- try
- {
- ThreadPoolBoundHandleOverlapped overlapped = preAllocated._overlapped;
-
- if (overlapped._boundHandle != null)
- throw new ArgumentException(SR.Argument_PreAllocatedAlreadyAllocated, nameof(preAllocated));
-
- overlapped._boundHandle = this;
-
- return overlapped._nativeOverlapped;
- }
- catch
- {
- preAllocated.Release();
- throw;
- }
- }
-
- /// <summary>
- /// Frees the unmanaged memory associated with a <see cref="NativeOverlapped"/> structure
- /// allocated by the <see cref="AllocateNativeOverlapped"/> method.
- /// </summary>
- /// <param name="overlapped">
- /// An unmanaged pointer to the <see cref="NativeOverlapped"/> structure to be freed.
- /// </param>
- /// <remarks>
- /// <note type="caution">
- /// You must call the <see cref="FreeNativeOverlapped(NativeOverlapped*)"/> method exactly once
- /// on every <see cref="NativeOverlapped"/> unmanaged pointer allocated using the
- /// <see cref="AllocateNativeOverlapped"/> method.
- /// If you do not call the <see cref="FreeNativeOverlapped(NativeOverlapped*)"/> method, you will
- /// leak memory. If you call the <see cref="FreeNativeOverlapped(NativeOverlapped*)"/> method more
- /// than once on the same <see cref="NativeOverlapped"/> unmanaged pointer, memory will be corrupted.
- /// </note>
- /// </remarks>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="overlapped"/> is <see langword="null"/>.
- /// </exception>
- /// <exception cref="ObjectDisposedException">
- /// This method was called after the <see cref="ThreadPoolBoundHandle"/> was disposed.
- /// </exception>
- [CLSCompliant(false)]
- public unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped)
- {
- ArgumentNullException.ThrowIfNull(overlapped);
-
- // Note: we explicitly allow FreeNativeOverlapped calls after the ThreadPoolBoundHandle has been Disposed.
-
- ThreadPoolBoundHandleOverlapped wrapper = GetOverlappedWrapper(overlapped);
-
- if (wrapper._boundHandle != this)
- throw new ArgumentException(SR.Argument_NativeOverlappedWrongBoundHandle, nameof(overlapped));
-
- if (wrapper._preAllocated != null)
- wrapper._preAllocated.Release();
- else
- Overlapped.Free(overlapped);
- }
-
- /// <summary>
- /// Returns the user-provided object specified when the <see cref="NativeOverlapped"/> instance was
- /// allocated using the <see cref="AllocateNativeOverlapped(IOCompletionCallback, object, object)"/>.
- /// </summary>
- /// <param name="overlapped">
- /// An unmanaged pointer to the <see cref="NativeOverlapped"/> structure from which to return the
- /// associated user-provided object.
- /// </param>
- /// <returns>
- /// A user-provided object that distinguishes this <see cref="NativeOverlapped"/>
- /// from other <see cref="NativeOverlapped"/> instances, otherwise, <see langword="null"/> if one was
- /// not specified when the instance was allocated using <see cref="AllocateNativeOverlapped"/>.
- /// </returns>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="overlapped"/> is <see langword="null"/>.
- /// </exception>
- [CLSCompliant(false)]
- public static unsafe object? GetNativeOverlappedState(NativeOverlapped* overlapped)
- {
- ArgumentNullException.ThrowIfNull(overlapped);
-
- ThreadPoolBoundHandleOverlapped wrapper = GetOverlappedWrapper(overlapped);
- Debug.Assert(wrapper._boundHandle != null);
- return wrapper._userState;
- }
-
- private static unsafe ThreadPoolBoundHandleOverlapped GetOverlappedWrapper(NativeOverlapped* overlapped)
- {
- ThreadPoolBoundHandleOverlapped wrapper;
- try
- {
- wrapper = (ThreadPoolBoundHandleOverlapped)Overlapped.Unpack(overlapped);
- }
- catch (NullReferenceException ex)
- {
- throw new ArgumentException(SR.Argument_NativeOverlappedAlreadyFree, nameof(overlapped), ex);
- }
-
- return wrapper;
- }
-
- public void Dispose()
- {
- // .NET Native's version of ThreadPoolBoundHandle that wraps the Win32 ThreadPool holds onto
- // native resources so it needs to be disposable. To match the contract, we are also disposable.
- // We also implement a disposable state to mimic behavior between this implementation and
- // .NET Native's version (code written against us, will also work against .NET Native's version).
- _isDisposed = true;
- }
- }
-}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Threading
+{
+ internal struct ThreadPoolCallbackWrapper
+ {
+ private Thread _currentThread;
+
+ public static ThreadPoolCallbackWrapper Enter()
+ {
+ Thread currentThread = Thread.CurrentThread;
+ if (!currentThread.IsThreadPoolThread)
+ {
+ currentThread.IsThreadPoolThread = true;
+#if TARGET_WINDOWS
+ // when using the Windows Threadpool, this is needed to increment the ThreadCount
+ ThreadPool.InitializeForThreadPoolThread();
+#endif
+ }
+ return new ThreadPoolCallbackWrapper
+ {
+ _currentThread = currentThread,
+ };
+ }
+
+ public void Exit(bool resetThread = true)
+ {
+ if (resetThread)
+ {
+ _currentThread.ResetThreadPoolThread();
+ }
+ }
+ }
+}
// us to return the thread to the pool or not.
//
int currentTickCount = Environment.TickCount;
- if (!ThreadPool.NotifyWorkItemComplete(threadLocalCompletionCountObject, currentTickCount))
+ if (!ThreadPool.NotifyWorkItemComplete(threadLocalCompletionCountObject!, currentTickCount))
{
// This thread is being parked and may remain inactive for a while. Transfer any thread-local work items
// to ensure that they would not be heavily delayed. Tell the caller that this thread was requested to stop
/// </summary>
private static readonly AutoResetEvent s_timerEvent = new AutoResetEvent(false);
+ // this means that it's in the s_scheduledTimers collection, not that it's the one which would run on the next TimeoutCallback
private bool _isScheduled;
private long _scheduledDueTimeMs;
-#pragma warning disable IDE0060
- private TimerQueue(int id)
- {
- }
-#pragma warning restore IDE0060
-
private static List<TimerQueue> InitializeScheduledTimerManager_Locked()
{
Debug.Assert(s_scheduledTimers == null);
return timers;
}
- private bool SetTimer(uint actualDuration)
+ private bool SetTimerPortable(uint actualDuration)
{
Debug.Assert((int)actualDuration >= 0);
long dueTimeMs = TickCount64 + (int)actualDuration;
internal sealed partial class TimerQueue
{
private static long TickCount64 => Environment.TickCount64;
+
+#pragma warning disable IDE0060
+ private TimerQueue(int id)
+ {
+ }
+#pragma warning restore IDE0060
+
+ private bool SetTimer(uint actualDuration) => SetTimerPortable(actualDuration);
}
}
{
internal sealed partial class TimerQueue
{
+ private TimerQueue(int id)
+ {
+ _id = id;
+ }
+
private static long TickCount64
{
get
}
}
}
+
+ private bool SetTimer(uint actualDuration) =>
+ ThreadPool.UseWindowsThreadPool ?
+ SetTimerWindowsThreadPool(actualDuration) :
+ SetTimerPortable(actualDuration);
}
}
private IntPtr _nativeTimer;
private readonly int _id;
- private TimerQueue(int id)
- {
- _id = id;
- }
-
+#pragma warning disable IDE0060 // Remove unused parameter
[UnmanagedCallersOnly]
- private static unsafe void TimerCallback(void* instance, void* context, void* timer)
+ private static unsafe void TimerCallbackWindowsThreadPool(void* instance, void* context, void* timer)
{
int id = (int)context;
var wrapper = ThreadPoolCallbackWrapper.Enter();
ThreadPool.IncrementCompletedWorkItemCount();
wrapper.Exit();
}
+#pragma warning restore IDE0060
- private unsafe bool SetTimer(uint actualDuration)
+ private unsafe bool SetTimerWindowsThreadPool(uint actualDuration)
{
if (_nativeTimer == IntPtr.Zero)
{
- _nativeTimer = Interop.Kernel32.CreateThreadpoolTimer(&TimerCallback, (IntPtr)_id, IntPtr.Zero);
+ _nativeTimer = Interop.Kernel32.CreateThreadpoolTimer(&TimerCallbackWindowsThreadPool, (IntPtr)_id, IntPtr.Zero);
if (_nativeTimer == IntPtr.Zero)
throw new OutOfMemoryException();
}
{
internal partial struct Win32ThreadPoolNativeOverlapped
{
- private unsafe class ExecutionContextCallbackArgs
+ private sealed unsafe class ExecutionContextCallbackArgs
{
internal uint _errorCode;
internal uint _bytesWritten;
internal Win32ThreadPoolNativeOverlapped* _overlapped;
- internal OverlappedData _data;
+ internal OverlappedData? _data;
}
}
}
{
internal partial struct Win32ThreadPoolNativeOverlapped
{
- internal class OverlappedData
+ internal sealed class OverlappedData
{
- internal GCHandle[] _pinnedData;
+ internal GCHandle[]? _pinnedData;
internal IOCompletionCallback? _callback;
internal object? _state;
internal ExecutionContext? _executionContext;
- internal ThreadPoolBoundHandle _boundHandle;
+ internal ThreadPoolBoundHandle? _boundHandle;
internal PreAllocatedOverlapped? _preAllocated;
internal bool _completed;
{
// Per-thread cache of the args object, so we don't have to allocate a new one each time.
[ThreadStatic]
- private static ExecutionContextCallbackArgs t_executionContextCallbackArgs;
+ private static ExecutionContextCallbackArgs? t_executionContextCallbackArgs;
- private static ContextCallback s_executionContextCallback;
- private static OverlappedData[] s_dataArray;
+ private static ContextCallback? s_executionContextCallback;
+ private static OverlappedData[]? s_dataArray;
private static int s_dataCount; // Current number of valid entries in _dataArray
private static IntPtr s_freeList; // Lock-free linked stack of free ThreadPoolNativeOverlapped instances.
internal OverlappedData Data
{
- get { return s_dataArray[_dataIndex]; }
+ get { return s_dataArray![_dataIndex]; }
}
- internal static unsafe Win32ThreadPoolNativeOverlapped* Allocate(IOCompletionCallback callback, object state, object pinData, PreAllocatedOverlapped preAllocated, bool flowExecutionControl)
+ internal static unsafe Win32ThreadPoolNativeOverlapped* Allocate(IOCompletionCallback callback, object? state, object? pinData, PreAllocatedOverlapped? preAllocated, bool flowExecutionControl)
{
Win32ThreadPoolNativeOverlapped* overlapped = AllocateNew();
try
// If we haven't stored this object in the array yet, do so now. Then we need to make another pass through
// the loop, in case another thread resized the array before we made this update.
- if (s_dataArray[dataIndex] == null)
+ if (s_dataArray![dataIndex] == null)
{
// Full fence so this write can't move past subsequent reads.
Interlocked.Exchange(ref dataArray![dataIndex], data);
}
}
- private void SetData(IOCompletionCallback callback, object state, object pinData, PreAllocatedOverlapped preAllocated, bool flowExecutionContext)
+ private void SetData(IOCompletionCallback callback, object? state, object? pinData, PreAllocatedOverlapped? preAllocated, bool flowExecutionContext)
{
Debug.Assert(callback != null);
return;
}
- ContextCallback callback = s_executionContextCallback;
+ ContextCallback? callback = s_executionContextCallback;
if (callback == null)
s_executionContextCallback = callback = OnExecutionContextCallback;
// Get an args object from the per-thread cache.
- ExecutionContextCallbackArgs args = t_executionContextCallbackArgs;
+ ExecutionContextCallbackArgs? args = t_executionContextCallbackArgs;
args ??= new ExecutionContextCallbackArgs();
t_executionContextCallbackArgs = null;
private static unsafe void OnExecutionContextCallback(object? state)
{
Debug.Assert(state != null);
- ExecutionContextCallbackArgs args = (ExecutionContextCallbackArgs)state;
+ ExecutionContextCallbackArgs args = (ExecutionContextCallbackArgs)state!;
uint errorCode = args._errorCode;
uint bytesWritten = args._bytesWritten;
Win32ThreadPoolNativeOverlapped* overlapped = args._overlapped;
- OverlappedData data = args._data;
+ OverlappedData data = args._data!;
// Put the args object back in the per-thread cache, now that we're done with it.
args._data = null;
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Win32.SafeHandles;
+using System.Diagnostics;
+using System.Runtime;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace System.Threading
+{
+ //
+ // Windows-specific implementation of ThreadPool
+ //
+ internal static class WindowsThreadPool
+ {
+ /// <summary>
+ /// The maximum number of threads in the default thread pool on Windows 10 as computed by
+ /// TppComputeDefaultMaxThreads(TppMaxGlobalPool).
+ /// </summary>
+ /// <remarks>
+ /// Note that Windows 8 and 8.1 used a different value: Math.Max(4 * Environment.ProcessorCount, 512).
+ /// </remarks>
+ private static readonly int MaxThreadCount = Math.Max(8 * Environment.ProcessorCount, 768);
+
+ private static IntPtr s_work;
+
+ private sealed class ThreadCountHolder
+ {
+ internal ThreadCountHolder() => Interlocked.Increment(ref s_threadCount);
+ ~ThreadCountHolder() => Interlocked.Decrement(ref s_threadCount);
+ }
+
+ [ThreadStatic]
+ private static ThreadCountHolder? t_threadCountHolder;
+ private static int s_threadCount;
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct WorkingThreadCounter
+ {
+ private readonly Internal.PaddingFor32 pad1;
+
+ public volatile int Count;
+
+ private readonly Internal.PaddingFor32 pad2;
+ }
+
+ // The number of threads executing work items in the Dispatch method
+ private static WorkingThreadCounter s_workingThreadCounter;
+
+ private static readonly ThreadInt64PersistentCounter s_completedWorkItemCounter = new ThreadInt64PersistentCounter();
+
+ [ThreadStatic]
+ private static object? t_completionCountObject;
+
+ internal static void InitializeForThreadPoolThread() => t_threadCountHolder = new ThreadCountHolder();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static void IncrementCompletedWorkItemCount() => ThreadInt64PersistentCounter.Increment(GetOrCreateThreadLocalCompletionCountObject());
+
+ internal static object GetOrCreateThreadLocalCompletionCountObject() =>
+ t_completionCountObject ?? CreateThreadLocalCompletionCountObject();
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static object CreateThreadLocalCompletionCountObject()
+ {
+ Debug.Assert(t_completionCountObject == null);
+
+ object threadLocalCompletionCountObject = s_completedWorkItemCounter.CreateThreadLocalCountObject();
+ t_completionCountObject = threadLocalCompletionCountObject;
+ return threadLocalCompletionCountObject;
+ }
+
+#pragma warning disable IDE0060 // Remove unused parameter
+ public static bool SetMaxThreads(int workerThreads, int completionPortThreads)
+ {
+ // Not supported at present
+ return false;
+ }
+#pragma warning restore IDE0060
+
+ public static void GetMaxThreads(out int workerThreads, out int completionPortThreads)
+ {
+ // Note that worker threads and completion port threads share the same thread pool.
+ // The total number of threads cannot exceed MaxThreadCount.
+ workerThreads = MaxThreadCount;
+ completionPortThreads = MaxThreadCount;
+ }
+
+#pragma warning disable IDE0060 // Remove unused parameter
+ public static bool SetMinThreads(int workerThreads, int completionPortThreads)
+ {
+ // Not supported at present
+ return false;
+ }
+#pragma warning restore IDE0060
+
+ public static void GetMinThreads(out int workerThreads, out int completionPortThreads)
+ {
+ workerThreads = 0;
+ completionPortThreads = 0;
+ }
+
+ public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads)
+ {
+ // Make sure we return a non-negative value if thread pool defaults are changed
+ int availableThreads = Math.Max(MaxThreadCount - s_workingThreadCounter.Count, 0);
+
+ workerThreads = availableThreads;
+ completionPortThreads = availableThreads;
+ }
+
+ /// <summary>
+ /// Gets the number of thread pool threads that currently exist.
+ /// </summary>
+ /// <remarks>
+ /// For a thread pool implementation that may have different types of threads, the count includes all types.
+ /// </remarks>
+ public static int ThreadCount => s_threadCount;
+
+ /// <summary>
+ /// Gets the number of work items that have been processed so far.
+ /// </summary>
+ /// <remarks>
+ /// For a thread pool implementation that may have different types of work items, the count includes all types.
+ /// </remarks>
+ public static long CompletedWorkItemCount => s_completedWorkItemCounter.Count;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static void NotifyWorkItemProgress() => IncrementCompletedWorkItemCount();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static bool NotifyWorkItemComplete(object threadLocalCompletionCountObject, int _ /*currentTimeMs*/)
+ {
+ ThreadInt64PersistentCounter.Increment(threadLocalCompletionCountObject);
+ return true;
+ }
+
+ internal static bool NotifyThreadBlocked() { return false; }
+ internal static void NotifyThreadUnblocked() { }
+
+ [UnmanagedCallersOnly]
+ private static void DispatchCallback(IntPtr instance, IntPtr context, IntPtr work)
+ {
+ var wrapper = ThreadPoolCallbackWrapper.Enter();
+
+ Debug.Assert(s_work == work);
+ Interlocked.Increment(ref s_workingThreadCounter.Count);
+ ThreadPoolWorkQueue.Dispatch();
+ Interlocked.Decrement(ref s_workingThreadCounter.Count);
+
+ // We reset the thread after executing each callback
+ wrapper.Exit(resetThread: false);
+ }
+
+ internal static unsafe void RequestWorkerThread()
+ {
+ if (s_work == IntPtr.Zero)
+ {
+ IntPtr work = Interop.Kernel32.CreateThreadpoolWork(&DispatchCallback, IntPtr.Zero, IntPtr.Zero);
+ if (work == IntPtr.Zero)
+ throw new OutOfMemoryException();
+
+ if (Interlocked.CompareExchange(ref s_work, work, IntPtr.Zero) != IntPtr.Zero)
+ Interop.Kernel32.CloseThreadpoolWork(work);
+ }
+
+ Interop.Kernel32.SubmitThreadpoolWork(s_work);
+ }
+
+ internal static RegisteredWaitHandle RegisterWaitForSingleObject(
+ WaitHandle waitObject,
+ WaitOrTimerCallback callBack,
+ object? state,
+ uint millisecondsTimeOutInterval,
+ bool executeOnlyOnce,
+ bool flowExecutionContext)
+ {
+ ArgumentNullException.ThrowIfNull(waitObject);
+ ArgumentNullException.ThrowIfNull(callBack);
+
+ var callbackHelper = new _ThreadPoolWaitOrTimerCallback(callBack, state, flowExecutionContext);
+ var registeredWaitHandle = new RegisteredWaitHandle(waitObject.SafeWaitHandle, callbackHelper, millisecondsTimeOutInterval, !executeOnlyOnce);
+
+ registeredWaitHandle.RestartWait();
+ return registeredWaitHandle;
+ }
+
+ private static unsafe void NativeOverlappedCallback(nint overlappedPtr) =>
+ IOCompletionCallbackHelper.PerformSingleIOCompletionCallback(0, 0, (NativeOverlapped*)overlappedPtr);
+
+ [SupportedOSPlatform("windows")]
+ public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped)
+ {
+ if (overlapped == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.overlapped);
+ }
+
+ // OS doesn't signal handle, so do it here
+ overlapped->InternalLow = (IntPtr)0;
+ // Both types of callbacks are executed on the same thread pool
+ return ThreadPool.UnsafeQueueUserWorkItem(NativeOverlappedCallback, (nint)overlapped, preferLocal: false);
+ }
+
+ [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")]
+ [SupportedOSPlatform("windows")]
+ public static bool BindHandle(IntPtr osHandle)
+ {
+ throw new PlatformNotSupportedException(SR.Arg_PlatformNotSupported); // Replaced by ThreadPoolBoundHandle.BindHandle
+ }
+
+#pragma warning disable IDE0060 // Remove unused parameter
+ [SupportedOSPlatform("windows")]
+ public static bool BindHandle(SafeHandle osHandle)
+ {
+ throw new PlatformNotSupportedException(SR.Arg_PlatformNotSupported); // Replaced by ThreadPoolBoundHandle.BindHandle
+ }
+#pragma warning restore IDE0060
+ }
+}
<EnabledFeatureSwitches>System.Reflection.NullabilityInfoContext.IsSupported</EnabledFeatureSwitches>
</TestConsoleAppSourceFiles>
</ItemGroup>
+ <ItemGroup Condition="'$(TargetsWindows)' == 'true'">
+ <TestConsoleAppSourceFiles Include="UseWindowsThreadPoolFalse.cs">
+ <DisabledFeatureSwitches>System.Threading.ThreadPool.UseWindowsThreadPool</DisabledFeatureSwitches>
+ </TestConsoleAppSourceFiles>
+ <TestConsoleAppSourceFiles Include="UseWindowsThreadPoolTrue.cs">
+ <EnabledFeatureSwitches>System.Threading.ThreadPool.UseWindowsThreadPool</EnabledFeatureSwitches>
+ </TestConsoleAppSourceFiles>
+ </ItemGroup>
<Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.targets))" />
</Project>
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Globalization;
+using System.Threading;
+
+/// <summary>
+/// Ensures setting UseWindowsThreadPool = false still works in a trimmed app.
+/// </summary>
+class Program
+{
+ static int Main(string[] args)
+ {
+ // SetMinThreads should work when using PortableThreadPool, this call should return true
+ if (!ThreadPool.SetMinThreads(1, 1))
+ {
+ return -1;
+ }
+
+ // SetMaxThreads should work when using PortableThreadPool, this call should return true
+ if (!ThreadPool.SetMaxThreads(10, 10))
+ {
+ return -1;
+ }
+
+ return 100;
+ }
+}
\ No newline at end of file
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Globalization;
+using System.Threading;
+
+/// <summary>
+/// Ensures setting UseWindowsThreadPool = true still works in a trimmed app.
+/// </summary>
+class Program
+{
+ static int Main(string[] args)
+ {
+ // SetMinThreads is not supported in WindowsThreadPool class, this call should return false
+ if (ThreadPool.SetMinThreads(1, 1))
+ {
+ return -1;
+ }
+
+ // SetMaxThreads is not supported in WindowsThreadPool class, this call should return false
+ if (ThreadPool.SetMaxThreads(10, 10))
+ {
+ return -1;
+ }
+
+ return 100;
+ }
+}
\ No newline at end of file
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{6A32653B-8FAC-4B01-A0E0-E6379DE90A1A}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Threading.ThreadPool.WindowsThreadPool.Tests", "tests\WindowsThreadPool\System.Threading.ThreadPool.WindowsThreadPool.Tests.csproj", "{2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
{71ACA28C-A4F1-4A07-A1B3-39DB86C11A75}.Checked|arm64.ActiveCfg = Debug|Any CPU
{71ACA28C-A4F1-4A07-A1B3-39DB86C11A75}.Checked|x64.ActiveCfg = Debug|Any CPU
{71ACA28C-A4F1-4A07-A1B3-39DB86C11A75}.Checked|x86.ActiveCfg = Debug|Any CPU
+ {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Debug|x64.Build.0 = Debug|Any CPU
+ {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Debug|x86.Build.0 = Debug|Any CPU
+ {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Release|x64.ActiveCfg = Release|Any CPU
+ {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Release|x64.Build.0 = Release|Any CPU
+ {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Release|x86.ActiveCfg = Release|Any CPU
+ {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Release|x86.Build.0 = Release|Any CPU
+ {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
+ {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Checked|Any CPU.Build.0 = Debug|Any CPU
+ {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Checked|x64.ActiveCfg = Debug|Any CPU
+ {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Checked|x64.Build.0 = Debug|Any CPU
+ {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Checked|x86.ActiveCfg = Debug|Any CPU
+ {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Checked|x86.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{BEACD8A1-3C09-450E-931F-561D44986BFB} = {3358539E-11F4-4294-8D94-A9A31E9F47F5}
- {53A0D29E-8633-4FF8-AB0E-86EF331DD379} = {3358539E-11F4-4294-8D94-A9A31E9F47F5}
{CD477FC8-AF27-46A2-A451-4A2D4C11600D} = {DB513386-C3B1-4891-92E1-1BE5D38562DC}
- {71ACA28C-A4F1-4A07-A1B3-39DB86C11A75} = {DB513386-C3B1-4891-92E1-1BE5D38562DC}
{54E0AB0F-EB35-49AB-9A38-7C09FD7EC373} = {1445280F-1F07-45C3-93BC-D4025720B5FE}
+ {3156246A-939D-4232-90B3-FDE03A14BA90} = {6A32653B-8FAC-4B01-A0E0-E6379DE90A1A}
{B5732A3E-15DB-4BAA-A44C-41A7C29C68F0} = {1445280F-1F07-45C3-93BC-D4025720B5FE}
{3A5BA59D-C1B6-492F-BF2C-FDE2C274E9B3} = {1445280F-1F07-45C3-93BC-D4025720B5FE}
- {3156246A-939D-4232-90B3-FDE03A14BA90} = {6A32653B-8FAC-4B01-A0E0-E6379DE90A1A}
{E8E31BA1-DC94-40F1-9606-6922941582DC} = {6A32653B-8FAC-4B01-A0E0-E6379DE90A1A}
{08FEC21B-875E-499A-B5CA-7D921B7A484B} = {6A32653B-8FAC-4B01-A0E0-E6379DE90A1A}
{54134937-585D-48D5-AB60-79493BE2C9E7} = {6A32653B-8FAC-4B01-A0E0-E6379DE90A1A}
+ {53A0D29E-8633-4FF8-AB0E-86EF331DD379} = {3358539E-11F4-4294-8D94-A9A31E9F47F5}
+ {71ACA28C-A4F1-4A07-A1B3-39DB86C11A75} = {DB513386-C3B1-4891-92E1-1BE5D38562DC}
+ {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE} = {DB513386-C3B1-4891-92E1-1BE5D38562DC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6124532E-4272-44A6-954B-A7B0FF9D694A}
<Compile Include="$(CommonTestPath)System\Threading\ThreadTestHelpers.cs"
Link="CommonTest\System\Threading\ThreadTestHelpers.cs" />
</ItemGroup>
+ <ItemGroup>
+ <RuntimeHostConfigurationOption Include="System.Threading.ThreadPool.UseWindowsThreadPool" Value="false" />
+ </ItemGroup>
</Project>
// Tests concurrent calls to ThreadPool.SetMinThreads. Invoked from the static constructor.
private static void ConcurrentInitializeTest()
{
- RemoteExecutor.Invoke(() =>
+ RemoteExecutor.Invoke((usePortableThreadPool) =>
{
int processorCount = Environment.ProcessorCount;
var countdownEvent = new CountdownEvent(processorCount);
{
countdownEvent.Signal();
countdownEvent.Wait(ThreadTestHelpers.UnexpectedTimeoutMilliseconds);
- Assert.True(ThreadPool.SetMinThreads(processorCount, processorCount));
+ if (Boolean.Parse(usePortableThreadPool))
+ {
+ Assert.True(ThreadPool.SetMinThreads(processorCount, processorCount));
+ }
};
var waitForThreadArray = new Action[processorCount];
{
waitForThread();
}
- }).Dispose();
+ }, UsePortableThreadPool.ToString()).Dispose();
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
Assert.True(c <= maxc);
}
- [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))]
+ [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported), nameof(UsePortableThreadPool))]
[ActiveIssue("https://github.com/mono/mono/issues/15164", TestRuntimes.Mono)]
public static void SetMinMaxThreadsTest()
{
}).Dispose();
}
- [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))]
+ [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported), nameof(UsePortableThreadPool))]
public static void SetMinMaxThreadsTest_ChangedInDotNetCore()
{
RemoteExecutor.Invoke(() =>
Assert.Equal(expectedMaxc, maxc);
}
- [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))]
+ [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported), nameof(UsePortableThreadPool))]
public static void SetMinThreadsTo0Test()
{
RemoteExecutor.Invoke(() =>
Assert.True(totalWorkCountToQueue >= 1);
waitForWorkStart = true;
scheduleWork();
- Assert.True(ThreadPool.ThreadCount >= totalWorkCountToQueue);
+ int threadCountLowerBound = UsePortableThreadPool ? totalWorkCountToQueue : 1;
+ Assert.True(ThreadPool.ThreadCount >= threadCountLowerBound);
int runningWorkItemCount = queuedWorkCount;
done.CheckedWait();
}
- [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))]
+ [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported), nameof(UsePortableThreadPool))]
public static void WorkerThreadStateResetTest()
{
RemoteExecutor.Invoke(() =>
}).Dispose();
}
- [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))]
+ [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported), nameof(UsePortableThreadPool))]
public static void SettingMinWorkerThreadsWillCreateThreadsUpToMinimum()
{
RemoteExecutor.Invoke(() =>
}
}
- [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))]
+ [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported), nameof(UsePortableThreadPool))]
public void ThreadPoolMinMaxThreadsEventTest()
{
// The ThreadPoolMinMaxThreads event is fired when the ThreadPool is created
public static bool IsThreadingAndRemoteExecutorSupported =>
PlatformDetection.IsThreadingSupported && RemoteExecutor.IsSupported;
+
+ private static bool GetUseWindowsThreadPool()
+ {
+ AppContext.TryGetSwitch("System.Threading.ThreadPool.UseWindowsThreadPool", out bool useWindowsThreadPool);
+ return useWindowsThreadPool;
+ }
+
+ private static bool UsePortableThreadPool { get; } = !GetUseWindowsThreadPool();
}
}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <IncludeRemoteExecutor>true</IncludeRemoteExecutor>
+ <!-- This test project is Windows only as it uses the Windows Threadpool -->
+ <TargetFramework>$(NetCoreAppCurrent)-windows</TargetFramework>
+ <TestRuntime>true</TestRuntime>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="..\ThreadPoolTests.cs" />
+ <Compile Include="..\RegisteredWaitTests.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="$(CommonTestPath)System\IO\TempFile.cs"
+ Link="CommonTest\System\IO\TempFile.cs" />
+ <Compile Include="$(CommonTestPath)System\Threading\ThreadTestHelpers.cs"
+ Link="CommonTest\System\Threading\ThreadTestHelpers.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <RuntimeHostConfigurationOption Include="System.Threading.ThreadPool.UseWindowsThreadPool" Value="true" />
+ </ItemGroup>
+</Project>
<Compile Include="$(BclSourcesRoot)\System\Threading\Monitor.Mono.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\ObjectHeader.Mono.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\Thread.Mono.cs" />
- <Compile Include="$(BclSourcesRoot)\System\Threading\ThreadPool.Mono.cs" />
<Compile Include="$(CommonPath)System\Collections\Generic\ArrayBuilder.cs">
<Link>CommonSystem\Collections\Generic\ArrayBuilder.cs</Link>
</Compile>
{
throw new PlatformNotSupportedException(SR.Arg_PlatformNotSupported); // Replaced by ThreadPoolBoundHandle.BindHandle
}
+
+ [Conditional("unnecessary")]
+ internal static void ReportThreadStatus(bool isWorking)
+ {
+
+ }
}
}
//
internal partial class TimerQueue
{
+ private static long TickCount64 => Environment.TickCount64;
private static List<TimerQueue>? s_scheduledTimers;
private static List<TimerQueue>? s_scheduledTimersToFire;
private static long s_shortestDueTimeMs = long.MaxValue;
// this means that it's in the s_scheduledTimers collection, not that it's the one which would run on the next TimeoutCallback
private bool _isScheduled;
private long _scheduledDueTimeMs;
-
private TimerQueue(int _)
{
}