diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj
index 2632a686aff7..c9c6140ea56d 100644
--- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj
+++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj
@@ -288,7 +288,6 @@
Common\Interop\Windows\OleAut32\Interop.VariantClear.cs
-
diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs
index 64ba59f672ec..c643a566d10b 100644
--- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs
+++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs
@@ -275,17 +275,17 @@ private bool SetApartmentStateUnchecked(ApartmentState state, bool throwOnError)
#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
diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.Windows.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.Windows.cs
deleted file mode 100644
index 279abdf1baa2..000000000000
--- a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.Windows.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-// 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();
- }
- }
- }
-}
diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs
index 031ff0cda2c8..e13fdeef3111 100644
--- a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs
+++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs
@@ -1,4 +1,4 @@
-// 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.
/*=============================================================================
@@ -18,30 +18,6 @@
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)_callback)(_state);
- }
public static partial class ThreadPool
{
@@ -52,12 +28,6 @@ internal static bool EnsureConfigInitialized()
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;
@@ -100,116 +70,5 @@ private static unsafe bool InitializeConfig()
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);
- }
-
- ///
- /// Gets the number of thread pool threads that currently exist.
- ///
- ///
- /// For a thread pool implementation that may have different types of threads, the count includes all types.
- ///
- public static int ThreadCount
- {
- get
- {
- return PortableThreadPool.ThreadPoolInstance.ThreadCount;
- }
- }
-
- ///
- /// Gets the number of work items that have been processed so far.
- ///
- ///
- /// For a thread pool implementation that may have different types of work items, the count includes all types.
- ///
- 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();
}
}
diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
index 88c0a7a3d8e9..4948020c8496 100644
--- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
+++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
@@ -43,6 +43,7 @@ The .NET Foundation licenses this file to you under the MIT license.
truefalsefalse
+ truefalse
@@ -272,6 +273,7 @@ The .NET Foundation licenses this file to you under the MIT license.
+
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj
index 3ba50b40df75..1e3c31218839 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj
@@ -26,12 +26,10 @@
- false
- true
+ true
- false
- true
+ truetrue
@@ -233,7 +231,6 @@
-
@@ -251,7 +248,6 @@
-
@@ -282,29 +278,13 @@
Interop\Windows\Kernel32\Interop.GetTickCount64.cs
-
- Interop\Windows\Kernel32\Interop.ThreadPool.cs
-
-
-
- Interop\Windows\Kernel32\Interop.Timer.cs
-
Interop\Windows\Kernel32\Interop.DynamicLoad.cs
-
- Interop\Windows\Kernel32\Interop.ThreadPoolIO.cs
-
-
-
-
-
-
-
-
-
+
+
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.NativeAot.cs
deleted file mode 100644
index 2fe7d1dd2c5d..000000000000
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.NativeAot.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-// 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)
- {
- }
- }
-}
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs
deleted file mode 100644
index 20011637e77e..000000000000
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs
+++ /dev/null
@@ -1,445 +0,0 @@
-// 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;
-
- ///
- /// The maximum number of threads in the default thread pool on Windows 10 as computed by
- /// TppComputeDefaultMaxThreads(TppMaxGlobalPool).
- ///
- ///
- /// Note that Windows 8 and 8.1 used a different value: Math.Max(4 * Environment.ProcessorCount, 512).
- ///
- 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;
- }
-
- ///
- /// Gets the number of thread pool threads that currently exist.
- ///
- ///
- /// For a thread pool implementation that may have different types of threads, the count includes all types.
- ///
- public static int ThreadCount => s_threadCount;
-
- ///
- /// Gets the number of work items that have been processed so far.
- ///
- ///
- /// For a thread pool implementation that may have different types of work items, the count includes all types.
- ///
- 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
- }
- }
-}
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPoolCallbackWrapper.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPoolCallbackWrapper.NativeAot.cs
similarity index 100%
rename from src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPoolCallbackWrapper.cs
rename to src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPoolCallbackWrapper.NativeAot.cs
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolPreAllocatedOverlapped.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolPreAllocatedOverlapped.cs
deleted file mode 100644
index e57f82aa24a5..000000000000
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolPreAllocatedOverlapped.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-// 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 _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);
- }
-}
diff --git a/src/coreclr/vm/comthreadpool.cpp b/src/coreclr/vm/comthreadpool.cpp
index 2d941b130991..daa8d48d3a30 100644
--- a/src/coreclr/vm/comthreadpool.cpp
+++ b/src/coreclr/vm/comthreadpool.cpp
@@ -43,23 +43,22 @@ FCIMPL4(INT32, ThreadPoolNative::GetNextConfigUInt32Value,
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;
diff --git a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Windows.xml b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Windows.xml
new file mode 100644
index 000000000000..29c26957ff6e
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Windows.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeThreadPoolIOHandle.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeThreadPoolIOHandle.cs
similarity index 90%
rename from src/coreclr/nativeaot/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeThreadPoolIOHandle.cs
rename to src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeThreadPoolIOHandle.cs
index d6bf95212d4e..5d8f7916bca0 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeThreadPoolIOHandle.cs
+++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeThreadPoolIOHandle.cs
@@ -6,7 +6,7 @@
namespace Microsoft.Win32.SafeHandles
{
- internal class SafeThreadPoolIOHandle : SafeHandle
+ internal sealed class SafeThreadPoolIOHandle : SafeHandle
{
public SafeThreadPoolIOHandle()
: base(IntPtr.Zero, true)
diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
index c7a6eed48f32..33fc345a9950 100644
--- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
+++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
@@ -57,6 +57,7 @@
+
@@ -1217,6 +1218,9 @@
+
+
+
@@ -1578,7 +1582,7 @@
Common\Interop\Windows\Kernel32\Interop.CancelIoEx.cs
-
+
Common\Interop\Windows\Kernel32\Interop.CompletionPort.cs
@@ -1596,6 +1600,15 @@
Common\Interop\Windows\Kernel32\Interop.CreateFile.cs
+
+ Interop\Windows\Kernel32\Interop.Timer.cs
+
+
+ Interop\Windows\Kernel32\Interop.ThreadPoolIO.cs
+
+
+ Interop\Windows\Kernel32\Interop.ThreadPool.cs
+
Common\Interop\Windows\NtDll\Interop.NtCreateFile.cs
@@ -2019,6 +2032,7 @@
+
@@ -2057,8 +2071,14 @@
+
+
+
+
+
+
@@ -2373,7 +2393,7 @@
-
+
@@ -2526,9 +2546,9 @@
-
-
-
+
+
+
@@ -2546,13 +2566,18 @@
-
-
+
+
+
+
+
-
-
+
+
+
+
diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Mono.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/CompleteWaitThreadPoolWorkItem.cs
similarity index 53%
rename from src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Mono.cs
rename to src/libraries/System.Private.CoreLib/src/System/Threading/CompleteWaitThreadPoolWorkItem.cs
index 6a6dd5b82e00..cc08d82e6340 100644
--- a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Mono.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/CompleteWaitThreadPoolWorkItem.cs
@@ -1,17 +1,15 @@
// 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);
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs
index 7adfe5a0b9ca..9ada201e134c 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs
@@ -457,5 +457,27 @@ private bool OnGen2GCCallback()
_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;
+ }
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Portable.cs
new file mode 100644
index 000000000000..c481ac6cefc0
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Portable.cs
@@ -0,0 +1,50 @@
+// 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
+{
+ ///
+ /// Represents pre-allocated state for native overlapped I/O operations.
+ ///
+ ///
+ 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;
+ }
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Unix.cs
similarity index 82%
rename from src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.cs
rename to src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Unix.cs
index 01a7aa93f012..d638e6773f2c 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Unix.cs
@@ -1,17 +1,16 @@
// 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
{
///
/// Represents pre-allocated state for native overlapped I/O operations.
///
///
- public sealed class PreAllocatedOverlapped : IDisposable, IDeferredDisposable
+ public sealed partial class PreAllocatedOverlapped : IDisposable, IDeferredDisposable
{
- internal readonly ThreadPoolBoundHandleOverlapped _overlapped;
- private DeferredDisposableLifetime _lifetime;
-
///
/// Initializes a new instance of the class, specifying
/// a delegate that is invoked when each asynchronous I/O operation is complete, a user-provided
@@ -52,6 +51,8 @@ public sealed class PreAllocatedOverlapped : IDisposable, IDeferredDisposable
{
}
+ private DeferredDisposableLifetime _lifetime;
+
///
/// Initializes a new instance of the class, specifying
/// a delegate that is invoked when each asynchronous I/O operation is complete, a user-provided
@@ -89,54 +90,35 @@ public sealed class PreAllocatedOverlapped : IDisposable, IDeferredDisposable
///
[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();
///
/// Frees the resources associated with this instance.
///
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);
}
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Windows.cs
new file mode 100644
index 000000000000..32149a729549
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Windows.cs
@@ -0,0 +1,159 @@
+// 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
+{
+ ///
+ /// Represents pre-allocated state for native overlapped I/O operations.
+ ///
+ ///
+ public sealed partial class PreAllocatedOverlapped : IDisposable, IDeferredDisposable
+ {
+ private DeferredDisposableLifetime _lifetime;
+
+ ///
+ /// Initializes a new instance of the 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.
+ ///
+ ///
+ /// An delegate that represents the callback method
+ /// invoked when each asynchronous I/O operation completes.
+ ///
+ ///
+ /// A user-provided object that distinguishes instance produced from this
+ /// object from other instances. Can be .
+ ///
+ ///
+ /// 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 .
+ ///
+ ///
+ /// The new instance can be passed to
+ /// , to produce
+ /// a instance that can be passed to the operating system in overlapped
+ /// I/O operations. A single instance can only be used for
+ /// a single native I/O operation at a time. However, the state stored in the
+ /// instance can be reused for subsequent native operations.
+ ///
+ /// The buffers specified in are pinned until is called.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// This method was called after the was disposed.
+ ///
+ [CLSCompliant(false)]
+ public PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData) :
+ this(callback, state, pinData, flowExecutionContext: true)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the 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.
+ ///
+ ///
+ /// An delegate that represents the callback method
+ /// invoked when each asynchronous I/O operation completes.
+ ///
+ ///
+ /// A user-provided object that distinguishes instance produced from this
+ /// object from other instances. Can be .
+ ///
+ ///
+ /// 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 .
+ ///
+ ///
+ /// The new instance can be passed to
+ /// , to produce
+ /// a instance that can be passed to the operating system in overlapped
+ /// I/O operations. A single instance can only be used for
+ /// a single native I/O operation at a time. However, the state stored in the
+ /// instance can be reused for subsequent native operations. ExecutionContext is not flowed to the invocation
+ /// of the callback.
+ ///
+ /// The buffers specified in are pinned until is called.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// This method was called after the was disposed.
+ ///
+ [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();
+ }
+ }
+
+ ///
+ /// Frees the resources associated with this instance.
+ ///
+ 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);
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.WindowsThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.WindowsThreadPool.cs
new file mode 100644
index 000000000000..0ad038cf84a8
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.WindowsThreadPool.cs
@@ -0,0 +1,44 @@
+// 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);
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs
index 359a9062d5db..b5672e243282 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs
@@ -1,4 +1,4 @@
-// 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;
@@ -15,66 +15,9 @@ namespace System.Threading
#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();
- ///
- /// The callback to execute when the wait on either times out or completes.
- ///
- internal _ThreadPoolWaitOrTimerCallback Callback { get; }
-
- ///
- /// The that was registered.
- ///
- internal SafeWaitHandle Handle { get; }
-
- ///
- /// The time this handle times out at in ms.
- ///
- 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;
- }
-
- ///
- /// Whether or not the wait is a repeating wait.
- ///
- internal bool Repeating { get; }
-
///
/// The the user passed in via .
///
@@ -109,7 +52,44 @@ internal void RestartTimeout()
///
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
@@ -205,7 +185,7 @@ private void SignalUserWaitHandle()
/// Perform the registered callback if the has not been signaled.
///
/// Whether or not the wait timed out.
- internal void PerformCallback(bool timedOut)
+ internal void PerformCallbackPortableCore(bool timedOut)
{
#if DEBUG
s_callbackLock.Acquire();
@@ -219,7 +199,7 @@ internal void PerformCallback(bool timedOut)
}
#endif
- _ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(Callback, timedOut);
+ _ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(Callback!, timedOut);
CompleteCallbackRequest();
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Unix.cs
new file mode 100644
index 000000000000..57adb4298663
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Unix.cs
@@ -0,0 +1,71 @@
+// 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
+{
+ ///
+ /// An object representing the registration of a via .
+ ///
+ public sealed partial class RegisteredWaitHandle : MarshalByRefObject
+ {
+ private SafeWaitHandle? _waitHandle;
+ private readonly _ThreadPoolWaitOrTimerCallback? _callbackHelper;
+ private readonly int _signedMillisecondsTimeout;
+ private bool _repeating;
+
+ ///
+ /// The callback to execute when the wait on either times out or completes.
+ ///
+ internal _ThreadPoolWaitOrTimerCallback? Callback
+ {
+ get => _callbackHelper;
+ }
+
+ ///
+ /// The that was registered.
+ ///
+ internal SafeWaitHandle Handle
+ {
+ get
+ {
+ Debug.Assert(_waitHandle != null);
+ return _waitHandle;
+ }
+ }
+
+ ///
+ /// The time this handle times out at in ms.
+ ///
+ internal int TimeoutTimeMs { get; private set; }
+
+ internal int TimeoutDurationMs
+ {
+ get => _signedMillisecondsTimeout;
+ }
+
+ internal bool IsInfiniteTimeout => TimeoutDurationMs == -1;
+
+ ///
+ /// Whether or not the wait is a repeating wait.
+ ///
+ internal bool Repeating
+ {
+ get => _repeating;
+ }
+
+ public bool Unregister(WaitHandle waitObject) => UnregisterPortableCore(waitObject);
+
+ ///
+ /// Perform the registered callback if the has not been signaled.
+ ///
+ /// Whether or not the wait timed out.
+ internal void PerformCallback(bool timedOut)
+ {
+ PerformCallbackPortableCore(timedOut);
+ }
+ }
+}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Windows.cs
new file mode 100644
index 000000000000..7f1d1309f105
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Windows.cs
@@ -0,0 +1,82 @@
+// 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;
+
+ ///
+ /// The callback to execute when the wait on either times out or completes.
+ ///
+ internal _ThreadPoolWaitOrTimerCallback? Callback
+ {
+ get => _callbackHelper;
+ }
+
+ ///
+ /// The that was registered.
+ ///
+ internal SafeWaitHandle Handle
+ {
+ get
+ {
+ Debug.Assert(_waitHandle != null);
+ return _waitHandle;
+ }
+ }
+
+ ///
+ /// The time this handle times out at in ms.
+ ///
+ internal int TimeoutTimeMs { get; private set; }
+
+ internal int TimeoutDurationMs
+ {
+ get => _signedMillisecondsTimeout;
+ }
+
+ internal bool IsInfiniteTimeout => TimeoutDurationMs == -1;
+
+ ///
+ /// Whether or not the wait is a repeating wait.
+ ///
+ internal bool Repeating
+ {
+ get => _repeating;
+ }
+
+ public bool Unregister(WaitHandle waitObject) =>
+ ThreadPool.UseWindowsThreadPool ?
+ UnregisterWindowsThreadPool(waitObject) :
+ UnregisterPortableCore(waitObject);
+
+ ///
+ /// Perform the registered callback if the has not been signaled.
+ ///
+ /// Whether or not the wait timed out.
+ internal void PerformCallback(bool timedOut)
+ {
+ if (ThreadPool.UseWindowsThreadPool)
+ {
+ PerformCallbackWindowsThreadPool(timedOut);
+ }
+ else
+ {
+ PerformCallbackPortableCore(timedOut);
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.WindowsThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.WindowsThreadPool.cs
new file mode 100644
index 000000000000..cad89f3f0709
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.WindowsThreadPool.cs
@@ -0,0 +1,220 @@
+// 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);
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Unix.cs
deleted file mode 100644
index 1d4e0c171496..000000000000
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Unix.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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);
- }
-}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Windows.cs
index 024110ea93b9..d1e959ce89c3 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Windows.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Windows.cs
@@ -10,9 +10,8 @@ namespace System.Threading
{
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)
{
@@ -28,14 +27,14 @@ public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapp
[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);
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Unix.cs
similarity index 53%
rename from src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs
rename to src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Unix.cs
index a9c4e038129a..3c5888a6f80d 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Unix.cs
@@ -1,93 +1,87 @@
// 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);
+ }
- ///
- /// Gets the number of thread pool threads that currently exist.
- ///
- ///
- /// For a thread pool implementation that may have different types of threads, the count includes all types.
- ///
- public static int ThreadCount => PortableThreadPool.ThreadPoolInstance.ThreadCount;
+ public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads)
+ {
+ PortableThreadPool.ThreadPoolInstance.GetAvailableThreads(out workerThreads, out completionPortThreads);
+ }
- ///
- /// Gets the number of work items that have been processed by the thread pool so far.
- ///
- ///
- /// For a thread pool implementation that may have different types of work items, the count includes all types.
- ///
- public static long CompletedWorkItemCount => PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static void NotifyWorkItemProgress()
+ {
+ PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress();
+ }
- ///
- /// This method is called to request a new thread pool worker to handle pending work.
- ///
- 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();
+ ///
+ /// This method is called to request a new thread pool worker to handle pending work.
+ ///
+ 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,
@@ -95,17 +89,51 @@ public static partial class ThreadPool
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);
+
+ ///
+ /// Gets the number of thread pool threads that currently exist.
+ ///
+ ///
+ /// For a thread pool implementation that may have different types of threads, the count includes all types.
+ ///
+ public static int ThreadCount
+ {
+ get
+ {
+ return PortableThreadPool.ThreadPoolInstance.ThreadCount;
+ }
}
+
+ ///
+ /// Gets the number of work items that have been processed so far.
+ ///
+ ///
+ /// For a thread pool implementation that may have different types of work items, the count includes all types.
+ ///
+ public static long CompletedWorkItemCount
+ {
+ get
+ {
+ return PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount;
+ }
+ }
+
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs
new file mode 100644
index 000000000000..6882b0482c01
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs
@@ -0,0 +1,220 @@
+// 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();
+ }
+ }
+
+ ///
+ /// This method is called to request a new thread pool worker to handle pending work.
+ ///
+ 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);
+ }
+
+ ///
+ /// Gets the number of thread pool threads that currently exist.
+ ///
+ ///
+ /// For a thread pool implementation that may have different types of threads, the count includes all types.
+ ///
+ public static int ThreadCount
+ {
+ get
+ {
+ return ThreadPool.UseWindowsThreadPool ? WindowsThreadPool.ThreadCount : PortableThreadPool.ThreadPoolInstance.ThreadCount;
+ }
+ }
+
+ ///
+ /// Gets the number of work items that have been processed so far.
+ ///
+ ///
+ /// For a thread pool implementation that may have different types of work items, the count includes all types.
+ ///
+ 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);
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Portable.cs
new file mode 100644
index 000000000000..5ce4e29d2ce5
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Portable.cs
@@ -0,0 +1,109 @@
+// 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
+ //
+
+ ///
+ /// 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.
+ ///
+ 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;
+ }
+ }
+}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Unix.cs
index 6f809959dbca..9643edf7f2d8 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Unix.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Unix.cs
@@ -6,15 +6,250 @@
namespace System.Threading
{
- public sealed partial class ThreadPoolBoundHandle
+ //
+ // Implementation of ThreadPoolBoundHandle that sits on top of the CLR's ThreadPool and Overlapped infrastructure
+ //
+
+ ///
+ /// 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.
+ ///
+ 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;
+ }
+
+ ///
+ /// Gets the bound operating system handle.
+ ///
+ ///
+ /// A object that holds the bound operating system handle.
+ ///
+ public SafeHandle Handle => _handle;
+
+ ///
+ /// Returns a for the specific handle,
+ /// which is bound to the system thread pool.
+ ///
+ ///
+ /// A object that holds the operating system handle. The
+ /// handle must have been opened for overlapped I/O on the unmanaged side.
+ ///
+ ///
+ /// for , which
+ /// is bound to the system thread pool.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// has been disposed.
+ ///
+ /// -or-
+ ///
+ /// does not refer to a valid I/O handle.
+ ///
+ /// -or-
+ ///
+ /// refers to a handle that has not been opened
+ /// for overlapped I/O.
+ ///
+ /// -or-
+ ///
+ /// refers to a handle that has already been bound.
+ ///
+ ///
+ /// This method should be called once per handle.
+ ///
+ /// -or-
+ ///
+ /// does not take ownership of ,
+ /// it remains the responsibility of the caller to call .
+ ///
+ 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);
}
+
+ ///
+ /// Returns an unmanaged pointer to a 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.
+ ///
+ ///
+ /// An delegate that represents the callback method
+ /// invoked when the asynchronous I/O operation completes.
+ ///
+ ///
+ /// A user-provided object that distinguishes this from other
+ /// instances. Can be .
+ ///
+ ///
+ /// 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 .
+ ///
+ ///
+ /// An unmanaged pointer to a structure.
+ ///
+ ///
+ ///
+ /// The unmanaged pointer returned by this method can be passed to the operating system in
+ /// overlapped I/O operations. The structure is fixed in
+ /// physical memory until is called.
+ ///
+ ///
+ /// The buffer or buffers specified in must be the same as those passed
+ /// to the unmanaged operating system function that performs the asynchronous I/O.
+ ///
+ ///
+ /// The buffers specified in are pinned for the duration of
+ /// the I/O operation.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// This method was called after the was disposed.
+ ///
+ [CLSCompliant(false)]
+ public unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) =>
+ AllocateNativeOverlappedPortableCore(callback, state, pinData);
+
+ ///
+ /// Returns an unmanaged pointer to a 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.
+ ///
+ ///
+ /// An delegate that represents the callback method
+ /// invoked when the asynchronous I/O operation completes.
+ ///
+ ///
+ /// A user-provided object that distinguishes this from other
+ /// instances. Can be .
+ ///
+ ///
+ /// 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 .
+ ///
+ ///
+ /// An unmanaged pointer to a structure.
+ ///
+ ///
+ ///
+ /// The unmanaged pointer returned by this method can be passed to the operating system in
+ /// overlapped I/O operations. The structure is fixed in
+ /// physical memory until is called.
+ ///
+ ///
+ /// The buffer or buffers specified in must be the same as those passed
+ /// to the unmanaged operating system function that performs the asynchronous I/O.
+ ///
+ ///
+ /// is not flowed to the invocation of the callback.
+ ///
+ ///
+ /// The buffers specified in are pinned for the duration of
+ /// the I/O operation.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// This method was called after the was disposed.
+ ///
+ [CLSCompliant(false)]
+ public unsafe NativeOverlapped* UnsafeAllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) =>
+ UnsafeAllocateNativeOverlappedPortableCore(callback, state, pinData);
+
+ ///
+ /// Returns an unmanaged pointer to a structure, using the callback,
+ /// state, and buffers associated with the specified object.
+ ///
+ ///
+ /// A object from which to create the NativeOverlapped pointer.
+ ///
+ ///
+ /// An unmanaged pointer to a structure.
+ ///
+ ///
+ ///
+ /// The unmanaged pointer returned by this method can be passed to the operating system in
+ /// overlapped I/O operations. The structure is fixed in
+ /// physical memory until is called.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// is currently in use for another I/O operation.
+ ///
+ ///
+ /// This method was called after the was disposed, or
+ /// this method was called after was disposed.
+ ///
+ ///
+ [CLSCompliant(false)]
+ public unsafe NativeOverlapped* AllocateNativeOverlapped(PreAllocatedOverlapped preAllocated) => AllocateNativeOverlappedPortableCore(preAllocated);
+
+ ///
+ /// Frees the unmanaged memory associated with a structure
+ /// allocated by the method.
+ ///
+ ///
+ /// An unmanaged pointer to the structure to be freed.
+ ///
+ ///
+ ///
+ /// You must call the method exactly once
+ /// on every unmanaged pointer allocated using the
+ /// method.
+ /// If you do not call the method, you will
+ /// leak memory. If you call the method more
+ /// than once on the same unmanaged pointer, memory will be corrupted.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// This method was called after the was disposed.
+ ///
+ [CLSCompliant(false)]
+ public unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped) => FreeNativeOverlappedPortableCore(overlapped);
+
+ ///
+ /// Returns the user-provided object specified when the instance was
+ /// allocated using the .
+ ///
+ ///
+ /// An unmanaged pointer to the structure from which to return the
+ /// associated user-provided object.
+ ///
+ ///
+ /// A user-provided object that distinguishes this
+ /// from other instances, otherwise, if one was
+ /// not specified when the instance was allocated using .
+ ///
+ ///
+ /// is .
+ ///
+ [CLSCompliant(false)]
+ public static unsafe object? GetNativeOverlappedState(NativeOverlapped* overlapped) => GetNativeOverlappedStatePortableCore(overlapped);
+
+ public void Dispose() => DisposePortableCore();
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Windows.cs
index f80107fcf1a6..97d77dd4f206 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Windows.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Windows.cs
@@ -1,18 +1,54 @@
-// 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
+ ///
+ /// 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.
+ ///
+ public sealed partial class ThreadPoolBoundHandle : IDisposable, IDeferredDisposable
{
+ private readonly SafeHandle _handle;
+ private readonly SafeThreadPoolIOHandle? _threadPoolHandle;
+ private DeferredDisposableLifetime _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);
+ }
+
+ ///
+ /// Gets the bound operating system handle.
+ ///
+ ///
+ /// A object that holds the bound operating system handle.
+ ///
+ 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
{
@@ -37,5 +73,262 @@ private static ThreadPoolBoundHandle BindHandleCore(SafeHandle handle)
return new ThreadPoolBoundHandle(handle);
}
+
+ ///
+ /// Returns a for the specific handle,
+ /// which is bound to the system thread pool.
+ ///
+ ///
+ /// A object that holds the operating system handle. The
+ /// handle must have been opened for overlapped I/O on the unmanaged side.
+ ///
+ ///
+ /// for , which
+ /// is bound to the system thread pool.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// has been disposed.
+ ///
+ /// -or-
+ ///
+ /// does not refer to a valid I/O handle.
+ ///
+ /// -or-
+ ///
+ /// refers to a handle that has not been opened
+ /// for overlapped I/O.
+ ///
+ /// -or-
+ ///
+ /// refers to a handle that has already been bound.
+ ///
+ ///
+ /// This method should be called once per handle.
+ ///
+ /// -or-
+ ///
+ /// does not take ownership of ,
+ /// it remains the responsibility of the caller to call .
+ ///
+ public static unsafe ThreadPoolBoundHandle BindHandle(SafeHandle handle) =>
+ ThreadPool.UseWindowsThreadPool ? BindHandleWindowsThreadPool(handle) : BindHandleCore(handle);
+
+ ///
+ /// Returns an unmanaged pointer to a 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.
+ ///
+ ///
+ /// An delegate that represents the callback method
+ /// invoked when the asynchronous I/O operation completes.
+ ///
+ ///
+ /// A user-provided object that distinguishes this from other
+ /// instances. Can be .
+ ///
+ ///
+ /// 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 .
+ ///
+ ///
+ /// An unmanaged pointer to a structure.
+ ///
+ ///
+ ///
+ /// The unmanaged pointer returned by this method can be passed to the operating system in
+ /// overlapped I/O operations. The structure is fixed in
+ /// physical memory until is called.
+ ///
+ ///
+ /// The buffer or buffers specified in must be the same as those passed
+ /// to the unmanaged operating system function that performs the asynchronous I/O.
+ ///
+ ///
+ /// The buffers specified in are pinned for the duration of
+ /// the I/O operation.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// This method was called after the was disposed.
+ ///
+ [CLSCompliant(false)]
+ public unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) =>
+ ThreadPool.UseWindowsThreadPool ?
+ AllocateNativeOverlappedWindowsThreadPool(callback, state, pinData) :
+ AllocateNativeOverlappedPortableCore(callback, state, pinData);
+
+ ///
+ /// Returns an unmanaged pointer to a 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.
+ ///
+ ///
+ /// An delegate that represents the callback method
+ /// invoked when the asynchronous I/O operation completes.
+ ///
+ ///
+ /// A user-provided object that distinguishes this from other
+ /// instances. Can be .
+ ///
+ ///
+ /// 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 .
+ ///
+ ///
+ /// An unmanaged pointer to a structure.
+ ///
+ ///
+ ///
+ /// The unmanaged pointer returned by this method can be passed to the operating system in
+ /// overlapped I/O operations. The structure is fixed in
+ /// physical memory until is called.
+ ///
+ ///
+ /// The buffer or buffers specified in must be the same as those passed
+ /// to the unmanaged operating system function that performs the asynchronous I/O.
+ ///
+ ///
+ /// is not flowed to the invocation of the callback.
+ ///
+ ///
+ /// The buffers specified in are pinned for the duration of
+ /// the I/O operation.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// This method was called after the was disposed.
+ ///
+ [CLSCompliant(false)]
+ public unsafe NativeOverlapped* UnsafeAllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) =>
+ ThreadPool.UseWindowsThreadPool ?
+ UnsafeAllocateNativeOverlappedWindowsThreadPool(callback, state, pinData) :
+ UnsafeAllocateNativeOverlappedPortableCore(callback, state, pinData);
+
+ ///
+ /// Returns an unmanaged pointer to a structure, using the callback,
+ /// state, and buffers associated with the specified object.
+ ///
+ ///
+ /// A object from which to create the NativeOverlapped pointer.
+ ///
+ ///
+ /// An unmanaged pointer to a structure.
+ ///
+ ///
+ ///
+ /// The unmanaged pointer returned by this method can be passed to the operating system in
+ /// overlapped I/O operations. The structure is fixed in
+ /// physical memory until is called.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// is currently in use for another I/O operation.
+ ///
+ ///
+ /// This method was called after the was disposed, or
+ /// this method was called after was disposed.
+ ///
+ ///
+ [CLSCompliant(false)]
+ public unsafe NativeOverlapped* AllocateNativeOverlapped(PreAllocatedOverlapped preAllocated) =>
+ ThreadPool.UseWindowsThreadPool ?
+ AllocateNativeOverlappedWindowsThreadPool(preAllocated) :
+ AllocateNativeOverlappedPortableCore(preAllocated);
+
+ ///
+ /// Frees the unmanaged memory associated with a structure
+ /// allocated by the method.
+ ///
+ ///
+ /// An unmanaged pointer to the structure to be freed.
+ ///
+ ///
+ ///
+ /// You must call the method exactly once
+ /// on every unmanaged pointer allocated using the
+ /// method.
+ /// If you do not call the method, you will
+ /// leak memory. If you call the method more
+ /// than once on the same unmanaged pointer, memory will be corrupted.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// This method was called after the was disposed.
+ ///
+ [CLSCompliant(false)]
+ public unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped)
+ {
+ if (ThreadPool.UseWindowsThreadPool)
+ {
+ FreeNativeOverlappedWindowsThreadPool(overlapped);
+ }
+ else
+ {
+ FreeNativeOverlappedPortableCore(overlapped);
+ }
+ }
+
+ ///
+ /// Returns the user-provided object specified when the instance was
+ /// allocated using the .
+ ///
+ ///
+ /// An unmanaged pointer to the structure from which to return the
+ /// associated user-provided object.
+ ///
+ ///
+ /// A user-provided object that distinguishes this
+ /// from other instances, otherwise, if one was
+ /// not specified when the instance was allocated using .
+ ///
+ ///
+ /// is .
+ ///
+ [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);
+ }
+ }
}
}
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolBoundHandle.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.WindowsThreadPool.cs
similarity index 74%
rename from src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolBoundHandle.cs
rename to src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.WindowsThreadPool.cs
index f6e56c38081f..26098e17f2e8 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolBoundHandle.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.WindowsThreadPool.cs
@@ -1,4 +1,4 @@
-// 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;
@@ -13,24 +13,9 @@ namespace System.Threading
//
// 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 _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);
@@ -53,15 +38,13 @@ public static unsafe ThreadPoolBoundHandle BindHandle(SafeHandle 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);
@@ -71,7 +54,7 @@ public static unsafe ThreadPoolBoundHandle BindHandle(SafeHandle handle)
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);
}
@@ -82,8 +65,7 @@ public static unsafe ThreadPoolBoundHandle BindHandle(SafeHandle handle)
}
}
- [CLSCompliant(false)]
- public unsafe NativeOverlapped* AllocateNativeOverlapped(PreAllocatedOverlapped preAllocated)
+ private unsafe NativeOverlapped* AllocateNativeOverlappedWindowsThreadPool(PreAllocatedOverlapped preAllocated)
{
ArgumentNullException.ThrowIfNull(preAllocated);
@@ -94,15 +76,15 @@ public static unsafe ThreadPoolBoundHandle BindHandle(SafeHandle handle)
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
{
@@ -114,8 +96,7 @@ public static unsafe ThreadPoolBoundHandle BindHandle(SafeHandle handle)
}
}
- [CLSCompliant(false)]
- public unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped)
+ private unsafe void FreeNativeOverlappedWindowsThreadPool(NativeOverlapped* overlapped)
{
ArgumentNullException.ThrowIfNull(overlapped);
@@ -124,7 +105,7 @@ public unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped)
if (!data._completed)
{
- Interop.Kernel32.CancelThreadpoolIo(_threadPoolHandle);
+ Interop.Kernel32.CancelThreadpoolIo(_threadPoolHandle!);
Release();
}
@@ -137,8 +118,7 @@ public unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped)
Win32ThreadPoolNativeOverlapped.Free(threadPoolOverlapped);
}
- [CLSCompliant(false)]
- public static unsafe object GetNativeOverlappedState(NativeOverlapped* overlapped)
+ private static unsafe object? GetNativeOverlappedStateWindowsThreadPool(NativeOverlapped* overlapped)
{
ArgumentNullException.ThrowIfNull(overlapped);
@@ -148,7 +128,7 @@ public static unsafe object GetNativeOverlappedState(NativeOverlapped* overlappe
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;
@@ -165,9 +145,10 @@ private static unsafe Win32ThreadPoolNativeOverlapped.OverlappedData GetOverlapp
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);
@@ -175,6 +156,7 @@ private static unsafe void OnNativeIOCompleted(IntPtr instance, IntPtr context,
Win32ThreadPoolNativeOverlapped.CompleteWithCallback(ioResult, (uint)numberOfBytesTransferred, overlapped);
ThreadPool.IncrementCompletedWorkItemCount();
+
wrapper.Exit();
}
@@ -188,13 +170,13 @@ private void Release()
_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
@@ -204,10 +186,10 @@ public void Dispose()
Dispose();
}
- void IDeferredDisposable.OnFinalRelease(bool disposed)
+ private void IDeferredDisposableOnFinalReleaseWindowsThreadPool(bool disposed)
{
if (disposed)
- _threadPoolHandle.Dispose();
+ _threadPoolHandle!.Dispose();
}
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.cs
deleted file mode 100644
index e4ec1fb94149..000000000000
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.cs
+++ /dev/null
@@ -1,331 +0,0 @@
-// 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
- //
-
- ///
- /// 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.
- ///
- public sealed partial class ThreadPoolBoundHandle : IDisposable
- {
- private readonly SafeHandle _handle;
- private bool _isDisposed;
-
- private ThreadPoolBoundHandle(SafeHandle handle)
- {
- _handle = handle;
- }
-
- ///
- /// Gets the bound operating system handle.
- ///
- ///
- /// A object that holds the bound operating system handle.
- ///
- public SafeHandle Handle => _handle;
-
- ///
- /// Returns a for the specific handle,
- /// which is bound to the system thread pool.
- ///
- ///
- /// A object that holds the operating system handle. The
- /// handle must have been opened for overlapped I/O on the unmanaged side.
- ///
- ///
- /// for , which
- /// is bound to the system thread pool.
- ///
- ///
- /// is .
- ///
- ///
- /// has been disposed.
- ///
- /// -or-
- ///
- /// does not refer to a valid I/O handle.
- ///
- /// -or-
- ///
- /// refers to a handle that has not been opened
- /// for overlapped I/O.
- ///
- /// -or-
- ///
- /// refers to a handle that has already been bound.
- ///
- ///
- /// This method should be called once per handle.
- ///
- /// -or-
- ///
- /// does not take ownership of ,
- /// it remains the responsibility of the caller to call .
- ///
- 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);
- }
-
- ///
- /// Returns an unmanaged pointer to a 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.
- ///
- ///
- /// An delegate that represents the callback method
- /// invoked when the asynchronous I/O operation completes.
- ///
- ///
- /// A user-provided object that distinguishes this from other
- /// instances. Can be .
- ///
- ///
- /// 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 .
- ///
- ///
- /// An unmanaged pointer to a structure.
- ///
- ///
- ///
- /// The unmanaged pointer returned by this method can be passed to the operating system in
- /// overlapped I/O operations. The structure is fixed in
- /// physical memory until is called.
- ///
- ///
- /// The buffer or buffers specified in must be the same as those passed
- /// to the unmanaged operating system function that performs the asynchronous I/O.
- ///
- ///
- /// The buffers specified in are pinned for the duration of
- /// the I/O operation.
- ///
- ///
- ///
- /// is .
- ///
- ///
- /// This method was called after the was disposed.
- ///
- [CLSCompliant(false)]
- public unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) =>
- AllocateNativeOverlapped(callback, state, pinData, flowExecutionContext: true);
-
- ///
- /// Returns an unmanaged pointer to a 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.
- ///
- ///
- /// An delegate that represents the callback method
- /// invoked when the asynchronous I/O operation completes.
- ///
- ///
- /// A user-provided object that distinguishes this from other
- /// instances. Can be .
- ///
- ///
- /// 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 .
- ///
- ///
- /// An unmanaged pointer to a structure.
- ///
- ///
- ///
- /// The unmanaged pointer returned by this method can be passed to the operating system in
- /// overlapped I/O operations. The structure is fixed in
- /// physical memory until is called.
- ///
- ///
- /// The buffer or buffers specified in must be the same as those passed
- /// to the unmanaged operating system function that performs the asynchronous I/O.
- ///
- ///
- /// is not flowed to the invocation of the callback.
- ///
- ///
- /// The buffers specified in are pinned for the duration of
- /// the I/O operation.
- ///
- ///
- ///
- /// is .
- ///
- ///
- /// This method was called after the was disposed.
- ///
- [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;
- }
-
- ///
- /// Returns an unmanaged pointer to a structure, using the callback,
- /// state, and buffers associated with the specified object.
- ///
- ///
- /// A object from which to create the NativeOverlapped pointer.
- ///
- ///
- /// An unmanaged pointer to a structure.
- ///
- ///
- ///
- /// The unmanaged pointer returned by this method can be passed to the operating system in
- /// overlapped I/O operations. The structure is fixed in
- /// physical memory until is called.
- ///
- ///
- ///
- /// is .
- ///
- ///
- /// is currently in use for another I/O operation.
- ///
- ///
- /// This method was called after the was disposed, or
- /// this method was called after was disposed.
- ///
- ///
- [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;
- }
- }
-
- ///
- /// Frees the unmanaged memory associated with a structure
- /// allocated by the method.
- ///
- ///
- /// An unmanaged pointer to the structure to be freed.
- ///
- ///
- ///
- /// You must call the method exactly once
- /// on every unmanaged pointer allocated using the
- /// method.
- /// If you do not call the method, you will
- /// leak memory. If you call the method more
- /// than once on the same unmanaged pointer, memory will be corrupted.
- ///
- ///
- ///
- /// is .
- ///
- ///
- /// This method was called after the was disposed.
- ///
- [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);
- }
-
- ///
- /// Returns the user-provided object specified when the instance was
- /// allocated using the .
- ///
- ///
- /// An unmanaged pointer to the structure from which to return the
- /// associated user-provided object.
- ///
- ///
- /// A user-provided object that distinguishes this
- /// from other instances, otherwise, if one was
- /// not specified when the instance was allocated using .
- ///
- ///
- /// is .
- ///
- [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;
- }
- }
-}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolCallbackWrapper.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolCallbackWrapper.cs
new file mode 100644
index 000000000000..664049c61ed4
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolCallbackWrapper.cs
@@ -0,0 +1,35 @@
+// 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();
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs
index 597cd4b46f2b..afdb8fb7d6bc 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs
@@ -935,7 +935,7 @@ internal static bool Dispatch()
// 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
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Portable.cs
index 40983768c823..346259aa412c 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Portable.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Portable.cs
@@ -20,15 +20,10 @@ internal sealed partial class TimerQueue : IThreadPoolWorkItem
///
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 InitializeScheduledTimerManager_Locked()
{
Debug.Assert(s_scheduledTimers == null);
@@ -50,7 +45,7 @@ private static List InitializeScheduledTimerManager_Locked()
return timers;
}
- private bool SetTimer(uint actualDuration)
+ private bool SetTimerPortable(uint actualDuration)
{
Debug.Assert((int)actualDuration >= 0);
long dueTimeMs = TickCount64 + (int)actualDuration;
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Unix.cs
index 005fa12ca6f2..140d136a12cb 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Unix.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Unix.cs
@@ -6,5 +6,13 @@ namespace System.Threading
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);
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Windows.cs
index 228cae763e26..48babb49981e 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Windows.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Windows.cs
@@ -7,6 +7,11 @@ namespace System.Threading
{
internal sealed partial class TimerQueue
{
+ private TimerQueue(int id)
+ {
+ _id = id;
+ }
+
private static long TickCount64
{
get
@@ -33,5 +38,10 @@ private static long TickCount64
}
}
}
+
+ private bool SetTimer(uint actualDuration) =>
+ ThreadPool.UseWindowsThreadPool ?
+ SetTimerWindowsThreadPool(actualDuration) :
+ SetTimerPortable(actualDuration);
}
}
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Timer.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.WindowsThreadPool.cs
similarity index 79%
rename from src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Timer.Windows.cs
rename to src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.WindowsThreadPool.cs
index 1f11b66ac14a..438e53db4eea 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Timer.Windows.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.WindowsThreadPool.cs
@@ -16,13 +16,9 @@ internal partial class TimerQueue
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();
@@ -30,12 +26,13 @@ private static unsafe void TimerCallback(void* instance, void* context, void* ti
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();
}
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.ExecutionContextCallbackArgs.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.ExecutionContextCallbackArgs.cs
similarity index 78%
rename from src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.ExecutionContextCallbackArgs.cs
rename to src/libraries/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.ExecutionContextCallbackArgs.cs
index 173a23775987..a3c8567d4bbb 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.ExecutionContextCallbackArgs.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.ExecutionContextCallbackArgs.cs
@@ -5,12 +5,12 @@ namespace System.Threading
{
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;
}
}
}
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.OverlappedData.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.OverlappedData.cs
similarity index 89%
rename from src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.OverlappedData.cs
rename to src/libraries/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.OverlappedData.cs
index 798f11584fbd..065e67fede2e 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.OverlappedData.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.OverlappedData.cs
@@ -8,13 +8,13 @@ namespace System.Threading
{
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;
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.cs
similarity index 92%
rename from src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.cs
rename to src/libraries/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.cs
index 26f453b499f2..02abf282c675 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.cs
@@ -12,10 +12,10 @@ internal partial struct Win32ThreadPoolNativeOverlapped
{
// 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.
@@ -25,10 +25,10 @@ internal partial struct Win32ThreadPoolNativeOverlapped
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
@@ -98,7 +98,7 @@ internal OverlappedData Data
// 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);
@@ -112,7 +112,7 @@ internal OverlappedData 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);
@@ -196,12 +196,12 @@ internal static unsafe void CompleteWithCallback(uint errorCode, uint bytesWritt
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;
@@ -217,12 +217,12 @@ internal static unsafe void CompleteWithCallback(uint errorCode, uint bytesWritt
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;
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WindowsThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WindowsThreadPool.cs
new file mode 100644
index 000000000000..d0637d2fd62f
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WindowsThreadPool.cs
@@ -0,0 +1,222 @@
+// 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
+ {
+ ///
+ /// The maximum number of threads in the default thread pool on Windows 10 as computed by
+ /// TppComputeDefaultMaxThreads(TppMaxGlobalPool).
+ ///
+ ///
+ /// Note that Windows 8 and 8.1 used a different value: Math.Max(4 * Environment.ProcessorCount, 512).
+ ///
+ 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;
+ }
+
+ ///
+ /// Gets the number of thread pool threads that currently exist.
+ ///
+ ///
+ /// For a thread pool implementation that may have different types of threads, the count includes all types.
+ ///
+ public static int ThreadCount => s_threadCount;
+
+ ///
+ /// Gets the number of work items that have been processed so far.
+ ///
+ ///
+ /// For a thread pool implementation that may have different types of work items, the count includes all types.
+ ///
+ 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
+ }
+}
diff --git a/src/libraries/System.Runtime/tests/TrimmingTests/System.Runtime.TrimmingTests.proj b/src/libraries/System.Runtime/tests/TrimmingTests/System.Runtime.TrimmingTests.proj
index 3ec268186391..4fe527aacc03 100644
--- a/src/libraries/System.Runtime/tests/TrimmingTests/System.Runtime.TrimmingTests.proj
+++ b/src/libraries/System.Runtime/tests/TrimmingTests/System.Runtime.TrimmingTests.proj
@@ -51,6 +51,14 @@
System.Reflection.NullabilityInfoContext.IsSupported
+
+
+ System.Threading.ThreadPool.UseWindowsThreadPool
+
+
+ System.Threading.ThreadPool.UseWindowsThreadPool
+
+
diff --git a/src/libraries/System.Runtime/tests/TrimmingTests/UseWindowsThreadPoolFalse.cs b/src/libraries/System.Runtime/tests/TrimmingTests/UseWindowsThreadPoolFalse.cs
new file mode 100644
index 000000000000..b6f89fc16b55
--- /dev/null
+++ b/src/libraries/System.Runtime/tests/TrimmingTests/UseWindowsThreadPoolFalse.cs
@@ -0,0 +1,29 @@
+// 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;
+
+///
+/// Ensures setting UseWindowsThreadPool = false still works in a trimmed app.
+///
+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
diff --git a/src/libraries/System.Runtime/tests/TrimmingTests/UseWindowsThreadPoolTrue.cs b/src/libraries/System.Runtime/tests/TrimmingTests/UseWindowsThreadPoolTrue.cs
new file mode 100644
index 000000000000..f0aeb5632fa7
--- /dev/null
+++ b/src/libraries/System.Runtime/tests/TrimmingTests/UseWindowsThreadPoolTrue.cs
@@ -0,0 +1,29 @@
+// 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;
+
+///
+/// Ensures setting UseWindowsThreadPool = true still works in a trimmed app.
+///
+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
diff --git a/src/libraries/System.Threading.ThreadPool/System.Threading.ThreadPool.sln b/src/libraries/System.Threading.ThreadPool/System.Threading.ThreadPool.sln
index 0dd5540a6fec..4e130a65939c 100644
--- a/src/libraries/System.Threading.ThreadPool/System.Threading.ThreadPool.sln
+++ b/src/libraries/System.Threading.ThreadPool/System.Threading.ThreadPool.sln
@@ -29,6 +29,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{1445280F-1F0
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
@@ -288,22 +290,41 @@ Global
{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}
diff --git a/src/libraries/System.Threading.ThreadPool/tests/System.Threading.ThreadPool.Tests.csproj b/src/libraries/System.Threading.ThreadPool/tests/System.Threading.ThreadPool.Tests.csproj
index d3359ebc0229..0cb21c9d3849 100644
--- a/src/libraries/System.Threading.ThreadPool/tests/System.Threading.ThreadPool.Tests.csproj
+++ b/src/libraries/System.Threading.ThreadPool/tests/System.Threading.ThreadPool.Tests.csproj
@@ -14,4 +14,7 @@
+
+
+
diff --git a/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs b/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs
index dfef12aa200b..a9b09f53895d 100644
--- a/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs
+++ b/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs
@@ -42,7 +42,7 @@ static ThreadPoolTests()
// 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);
@@ -51,7 +51,10 @@ private static void ConcurrentInitializeTest()
{
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];
@@ -66,7 +69,7 @@ private static void ConcurrentInitializeTest()
{
waitForThread();
}
- }).Dispose();
+ }, UsePortableThreadPool.ToString()).Dispose();
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
@@ -97,7 +100,7 @@ public static void GetAvailableThreadsTest()
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()
{
@@ -161,7 +164,7 @@ public static void SetMinMaxThreadsTest()
}).Dispose();
}
- [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))]
+ [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported), nameof(UsePortableThreadPool))]
public static void SetMinMaxThreadsTest_ChangedInDotNetCore()
{
RemoteExecutor.Invoke(() =>
@@ -222,7 +225,7 @@ private static void VerifyMaxThreads(int expectedMaxw, int expectedMaxc)
Assert.Equal(expectedMaxc, maxc);
}
- [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))]
+ [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported), nameof(UsePortableThreadPool))]
public static void SetMinThreadsTo0Test()
{
RemoteExecutor.Invoke(() =>
@@ -537,7 +540,8 @@ public void MetricsTest()
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;
@@ -701,7 +705,7 @@ public static void WorkQueueDepletionTest()
done.CheckedWait();
}
- [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))]
+ [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported), nameof(UsePortableThreadPool))]
public static void WorkerThreadStateResetTest()
{
RemoteExecutor.Invoke(() =>
@@ -785,7 +789,7 @@ public static void WorkerThreadStateResetTest()
}).Dispose();
}
- [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))]
+ [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported), nameof(UsePortableThreadPool))]
public static void SettingMinWorkerThreadsWillCreateThreadsUpToMinimum()
{
RemoteExecutor.Invoke(() =>
@@ -1105,7 +1109,7 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData)
}
}
- [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))]
+ [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported), nameof(UsePortableThreadPool))]
public void ThreadPoolMinMaxThreadsEventTest()
{
// The ThreadPoolMinMaxThreads event is fired when the ThreadPool is created
@@ -1158,5 +1162,13 @@ public void ThreadPoolMinMaxThreadsEventTest()
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();
}
}
diff --git a/src/libraries/System.Threading.ThreadPool/tests/WindowsThreadPool/System.Threading.ThreadPool.WindowsThreadPool.Tests.csproj b/src/libraries/System.Threading.ThreadPool/tests/WindowsThreadPool/System.Threading.ThreadPool.WindowsThreadPool.Tests.csproj
new file mode 100644
index 000000000000..f39bea77d0fb
--- /dev/null
+++ b/src/libraries/System.Threading.ThreadPool/tests/WindowsThreadPool/System.Threading.ThreadPool.WindowsThreadPool.Tests.csproj
@@ -0,0 +1,21 @@
+
+
+ true
+
+ $(NetCoreAppCurrent)-windows
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj
index a46986579e04..a9b7ee6424b2 100644
--- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj
+++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj
@@ -267,7 +267,6 @@
-
CommonSystem\Collections\Generic\ArrayBuilder.cs
diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs
index 2ad06cd7b497..67dc4147f7e4 100644
--- a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs
+++ b/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs
@@ -161,5 +161,11 @@ public static bool BindHandle(SafeHandle osHandle)
{
throw new PlatformNotSupportedException(SR.Arg_PlatformNotSupported); // Replaced by ThreadPoolBoundHandle.BindHandle
}
+
+ [Conditional("unnecessary")]
+ internal static void ReportThreadStatus(bool isWorking)
+ {
+
+ }
}
}
diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.Mono.cs
index 0682f3ef246c..f4c7f64f89fe 100644
--- a/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.Mono.cs
+++ b/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.Mono.cs
@@ -16,6 +16,7 @@ namespace System.Threading
//
internal partial class TimerQueue
{
+ private static long TickCount64 => Environment.TickCount64;
private static List? s_scheduledTimers;
private static List? s_scheduledTimersToFire;
private static long s_shortestDueTimeMs = long.MaxValue;
@@ -23,7 +24,6 @@ internal partial class TimerQueue
// 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 _)
{
}