<PackageReference Include="NUnit" Version="3.7.1" />

ThreadUtility

public static class ThreadUtility
ThreadUtility provides a set of static methods convenient for working with threads.
using System; using System.ComponentModel; using System.Runtime.InteropServices; using System.Security; using System.Threading; namespace NUnit.Framework.Internal { public static class ThreadUtility { private sealed class DelayClosure { private readonly Timer _timer; private readonly WaitCallback _threadPoolWork; private readonly object _state; public DelayClosure(int milliseconds, WaitCallback threadPoolWork, object state) { _threadPoolWork = threadPoolWork; _state = state; _timer = new Timer(Callback, null, -1, -1); _timer.Change(milliseconds, -1); } private void Callback(object _) { _timer.Dispose(); _threadPoolWork(_state); } } private sealed class CheckOnAbortingThreadState { public Thread Thread { get; } public int NativeId { get; } public CheckOnAbortingThreadState(Thread thread, int nativeId) { Thread = thread; NativeId = nativeId; } } private enum WM : uint { CLOSE = 16 } private const int ThreadAbortedCheckDelay = 100; private static bool isNotOnWindows; public static void Delay(int milliseconds, WaitCallback threadPoolWork, object state = null) { new DelayClosure(milliseconds, threadPoolWork, state); } public static void Abort(Thread thread, int nativeId = 0) { if (nativeId != 0) DislodgeThreadInNativeMessageWait(thread, nativeId); thread.Abort(); } public static void Kill(Thread thread, int nativeId = 0) { Kill(thread, null, nativeId); } public static void Kill(Thread thread, object stateInfo, int nativeId = 0) { if (nativeId != 0) DislodgeThreadInNativeMessageWait(thread, nativeId); try { if (stateInfo == null) thread.Abort(); else thread.Abort(stateInfo); } catch (ThreadStateException) { thread.Resume(); } if ((thread.ThreadState & ThreadState.WaitSleepJoin) != 0) thread.Interrupt(); } private static void DislodgeThreadInNativeMessageWait(Thread thread, int nativeId) { if (nativeId == 0) throw new ArgumentOutOfRangeException("nativeId", "Native thread ID must not be zero."); Delay(100, CheckOnAbortingThread, new CheckOnAbortingThreadState(thread, nativeId)); } private static void CheckOnAbortingThread(object state) { CheckOnAbortingThreadState checkOnAbortingThreadState = (CheckOnAbortingThreadState)state; switch (checkOnAbortingThreadState.Thread.ThreadState) { case ThreadState.Aborted: return; case ThreadState.AbortRequested: PostThreadCloseMessage(checkOnAbortingThreadState.NativeId); break; } Delay(100, CheckOnAbortingThread, state); } [SecuritySafeCritical] public static int GetCurrentThreadNativeId() { if (!isNotOnWindows) try { return GetCurrentThreadId(); } catch (EntryPointNotFoundException) { isNotOnWindows = true; return 0; } return 0; } [SecuritySafeCritical] private static void PostThreadCloseMessage(int nativeId) { if (!PostThreadMessage(nativeId, WM.CLOSE, IntPtr.Zero, IntPtr.Zero)) { int lastWin32Error = Marshal.GetLastWin32Error(); if (lastWin32Error != 1444) throw new Win32Exception(lastWin32Error); } } [DllImport("kernel32.dll")] private static extern int GetCurrentThreadId(); [DllImport("user32.dll", SetLastError = true)] private static extern bool PostThreadMessage(int id, WM msg, IntPtr wParam, IntPtr lParam); } }