在.NET Core世界中终止线程的替代方法?

时间:2019-10-01 20:18:15

标签: c# multithreading asp.net-core .net-core

在我的ASP.NET Core 2.2应用程序中,我不得不调用一个第三方库,该库易于将CPU推到100%并基本上挂起了计算机-每月至少发生两次。我无权访问源,供应商也不会修复它。

我对这个问题的解决方案是在.NET Framework 4.x Web服务中隔离此第三方库,在该服务中我可以调用Thread。如果发现问题,则中止该线程。将其隔离在.NET Framework服务而不是.NET Core中的原因是因为后者不支持Thread.Abort。当前的解决方案虽然不理想,但可以使用。甚至知道Thread.Abort都可能导致不稳定(到目前为止)。

出于性能原因,我宁愿不要将库隔离。但是到目前为止,我还没有找到一种方法来杀死.NET Core项目中的失控线程(或Task)。

我可以使用哪些替代方案?

2 个答案:

答案 0 :(得分:4)

我也同意the comment,在这种情况下,拆除整个过程可能是更干净的解决方案。但是,如果您喜欢坚持使用Thread.Abort方法,则至少可以使用Win32 Interop调用非托管的TerminateThread API,至少可以使用.NET Core for Windows来实现它。

下面是这样做的一个示例(警告:几乎未经测试)。

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace CoreConsole
{
    class Program
    {
        static async Task Main(string[] args)
        {
            try
            {
                using (var longRunningThread = new LongRunningThread(() => Thread.Sleep(5000)))
                {
                    await Task.Delay(2500);
                    longRunningThread.Abort();
                    await longRunningThread.Completion;
                    Console.WriteLine("Finished");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"{ex.Message}");
            }
        }
    }

    public class LongRunningThread : IDisposable
    {
        readonly Thread _thread;

        IntPtr _threadHandle = IntPtr.Zero;

        readonly TaskCompletionSource<bool> _threadEndTcs;

        readonly Task _completionTask;

        public Task Completion { get { return _completionTask; } }

        readonly object _lock = new object();

        public LongRunningThread(Action action)
        {
            _threadEndTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);

            _thread = new Thread(_ =>
            {
                try
                {
                    var hCurThread = NativeMethods.GetCurrentThread();
                    var hCurProcess = NativeMethods.GetCurrentProcess();
                    if (!NativeMethods.DuplicateHandle(
                        hCurProcess, hCurThread, hCurProcess, out _threadHandle,
                        0, false, NativeMethods.DUPLICATE_SAME_ACCESS))
                    {
                        throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
                    }

                    action();

                    _threadEndTcs.TrySetResult(true);
                }
                catch (Exception ex)
                {
                    _threadEndTcs.TrySetException(ex);
                }
            });

            async Task waitForThreadEndAsync()
            {
                try
                {
                    await _threadEndTcs.Task.ConfigureAwait(false);
                }
                finally
                {
                    // we use TaskCreationOptions.RunContinuationsAsynchronously for _threadEndTcs
                    // to mitigate possible deadlocks here
                    _thread.Join();
                }
            }

            _thread.IsBackground = true;
            _thread.Start();

            _completionTask = waitForThreadEndAsync();
        }

        public void Abort()
        {
            if (Thread.CurrentThread == _thread)
                throw new InvalidOperationException();

            lock (_lock)
            {
                if (!_threadEndTcs.Task.IsCompleted)
                {
                    _threadEndTcs.TrySetException(new ThreadTerminatedException());
                    if (NativeMethods.TerminateThread(_threadHandle, uint.MaxValue))
                    {
                        NativeMethods.WaitForSingleObject(_threadHandle, NativeMethods.INFINITE);
                    }
                    else
                    {
                        throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
                    }
                }
            }
        }

        public void Dispose()
        {
            if (Thread.CurrentThread == _thread)
                throw new InvalidOperationException();

            lock (_lock)
            {
                try
                {
                    if (_thread.IsAlive)
                    {
                        Abort();
                        _thread.Join();
                    }
                }
                finally
                {
                    GC.SuppressFinalize(this);
                    Cleanup();
                }
            }
        }

        ~LongRunningThread()
        {
            Cleanup();
        }

        void Cleanup()
        {
            if (_threadHandle != IntPtr.Zero)
            {
                NativeMethods.CloseHandle(_threadHandle);
                _threadHandle = IntPtr.Zero;
            }
        }
    }

    public class ThreadTerminatedException : SystemException
    {
        public ThreadTerminatedException() : base(nameof(ThreadTerminatedException)) { }
    }

    internal static class NativeMethods
    {
        public const uint DUPLICATE_SAME_ACCESS = 2;
        public const uint INFINITE = uint.MaxValue;

        [DllImport("kernel32.dll")]
        public static extern IntPtr GetCurrentThread();

        [DllImport("kernel32.dll")]
        public static extern IntPtr GetCurrentProcess();

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool CloseHandle(IntPtr handle);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool DuplicateHandle(IntPtr hSourceProcessHandle,
           IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle,
           uint dwDesiredAccess, bool bInheritHandle, uint dwOptions);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool TerminateThread(IntPtr hThread, uint dwExitCode);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
    }

}

答案 1 :(得分:2)

您可以降低Thread.Priority(在Core 3.0中可用)。当没有其他需要的CPU周期时,它将仍然使用所有可用的CPU周期,但是系统将响应更快。