在我的ASP.NET Core 2.2应用程序中,我不得不调用一个第三方库,该库易于将CPU推到100%并基本上挂起了计算机-每月至少发生两次。我无权访问源,供应商也不会修复它。
我对这个问题的解决方案是在.NET Framework 4.x Web服务中隔离此第三方库,在该服务中我可以调用Thread。如果发现问题,则中止该线程。将其隔离在.NET Framework服务而不是.NET Core中的原因是因为后者不支持Thread.Abort。当前的解决方案虽然不理想,但可以使用。甚至知道Thread.Abort都可能导致不稳定(到目前为止)。
出于性能原因,我宁愿不要将库隔离。但是到目前为止,我还没有找到一种方法来杀死.NET Core项目中的失控线程(或Task)。
我可以使用哪些替代方案?
答案 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周期,但是系统将响应更快。