C# - 线程中止异常(Thread Abort Exception)重新抛出自身
Posted
技术标签:
【中文标题】C# - 线程中止异常(Thread Abort Exception)重新抛出自身【英文标题】:C# - Thread Abort Exception (Thread Abort Exception) rethrowing itself 【发布时间】:2014-08-11 08:28:45 【问题描述】:我有当前代码:
class Program
private static void Main()
while (true)
try
Thread.CurrentThread.Abort();
catch (ThreadAbortException)
Console.WriteLine("Abort!");
Thread.ResetAbort();
Console.WriteLine("now waiting");
Console.ReadKey();
现在我知道 ResetAbort
方法应该防止 ThreadAbortException
继续重新抛出自身,即使 catch
语句正在捕获它,但我的问题是:
如果有人可以使用ResetAbort
方法,那么异常重新抛出自身的意义何在?
用户可以这样做
catch (ThreadAbortException ex)
Console.WriteLine("Abort!");
throw ex;
【问题讨论】:
不要“抛出前任”。只是“扔”。前者将重置堆栈跟踪。 您的推理中的基本缺陷是您希望程序员总是为 TAE 编写一个 catch 子句。他们当然没有。至少不是因为他们不希望被那样对待。 【参考方案1】:Thread.ResetAbort()
不适合常用。如果您不明白为什么会发生tad abort,它可能会导致不良行为。正因为如此,并且可能是为了使 ASP.NET 在共享主机环境中稳定,调用Thread.ResetAbort()
需要SecurityPermissionFlag.ControlThread
权限
MSDN Link
【讨论】:
另外,如果线程中止时间过长,可能会出现粗鲁的线程中止,ResetAbort
不理会。span>
另外,如果你做了太多的中止(就像你在一种无限循环中做的那样),IIS 环境会认为你是一个坏程序并关闭你。这可能是您看到的“虚假”线程中止的原因。【参考方案2】:
ThreadAbortException
重新抛出自身的目的是确保线程终止,除非用户显式调用 ResetAbort
。
让我解释一下:
try
// ... Thread abort happens somewhere in here
catch (Exception ex)
_log.Error(ex);
这里有一个典型的代码示例,可确保不会从try
块内部传播异常。我知道捕捉Exception
是不好的做法,但是这样的代码仍然存在。
如果您在线程位于try
块内时调用Abort
,您仍然希望它中止。你不能指望用户到处写这种代码:
try
// ... Thread abort happens somewhere in here
catch (ThreadAbortException)
throw; // No, you'll NEVER see code like this in real life
catch (Exception ex)
_log.Error(ex);
因此,为了提供某种可靠的Abort
,必须自动重新抛出异常,否则很容易被意外丢弃。
ResetAbort
适用于非常罕见的情况,即您专门检测到线程中止,并且您确切知道它发生的原因,并且想要阻止它。
不用说,这种用例极为罕见。运行时以一种非常特殊的方式处理线程中止,您应该尽可能避免它们。见鬼,它们甚至不像你指出的那样可靠,所有这些讨论都忽略了CERs,这让事情变得更糟。
【讨论】:
【参考方案3】:关键是要定义一个默认行为,在该行为中重新抛出异常,看看用户有什么机会继续线程。
另外ResetAbort
有安全需求,不能被任何代码调用。
【讨论】:
我不是反对者,但您的原始答案大不相同。 @DavidCrowell 第二行不见了。【参考方案4】:因为中止线程并不一定意味着会引发异常。对于 Abort Procedure,catch (ThreadAbortException)
块只是代码的另一个关键区域。它只为我们提供了一种线程安全且方便的方法来检测当前线程是否被中止(并且可能还传递了一些状态),以防我们想做一些特殊的事情。除此之外,它就像任何其他关键区域(如 finally 块)一样,它将在执行后终止线程。
同时,在您的示例中,Abort
被称为 同步(这实际上是安全的),在这种情况下 它非常类似于抛出例外。只有在从另一个线程异步调用它时事情才会变得有趣和危险,因为 Abort 过程比仅仅抛出异常更复杂:本质上,首先,线程被标记为被中止,然后是关键代码区域(例如 finally 块)被执行,然后才抛出异常,如果 AbortRequested 标志仍然设置在线程上,等等。
下面的代码通过在不捕获任何异常的情况下恢复中止的线程来说明这一事实:
var inFinally = new ManualResetEvent(false);
var abortCalled = new ManualResetEvent(false);
var t = new Thread(_ =>
Console.WriteLine("Thread started..");
try
finally
inFinally.Set();
abortCalled.WaitOne();
Console.WriteLine(" ThreadState (before): " + Thread.CurrentThread.ThreadState);
// This isn't thread safe, and ugly?
if ((Thread.CurrentThread.ThreadState & ThreadState.AbortRequested) != 0)
Thread.ResetAbort();
Console.WriteLine(" ThreadState (after): " + Thread.CurrentThread.ThreadState);
Console.WriteLine("Executed because we called Thread.ResetAbort()");
);
t.Start();
inFinally.WaitOne();
// Call from another thread because Abort()
// blocks while in finally block
ThreadPool.QueueUserWorkItem(_ => t.Abort());
while ((t.ThreadState & ThreadState.AbortRequested) == 0)
Thread.Sleep(1);
abortCalled.Set();
Console.ReadLine();
// Output:
//--------------------------------------------------
// Thread started..
// ThreadState (before): AbortRequested
// ThreadState (after): Running
// Executed because we called Thread.ResetAbort()
现在,我必须说实话:我不完全确定如何使用此功能并创建有用的东西。但听起来 Thread.Abort API 曾经(可能仍然是,我不知道)用于促进 ASP.NET 等框架中的线程和 AppDomain 重用。
在 Joe Duffy 的一篇博客文章中,Managed code and asynchronous exception hardening,他谈到了 ResetAbort 和 Abort API:
一些框架基础架构,尤其是 ASP.NET,甚至会中止单个线程 经常不卸载域。他们支持 ThreadAbortExceptions,调用 线程上的 ResetAbort 并重用它或将其返回到 CLR ThreadPool。
我可以想象它可以在框架中用于重用托管线程,从而减少开销。然而,这种容易被误解的 API 在用户代码中引入的问题(糟糕的线程同步设计、糟糕的异常处理、死锁等)使得 Abort 和 ResetAbort 调用变得麻烦多于有用。
【讨论】:
以上是关于C# - 线程中止异常(Thread Abort Exception)重新抛出自身的主要内容,如果未能解决你的问题,请参考以下文章
可以像中止一个Thread(Thread.Abort方法)一样中止一个Task吗?
异常记录 System.Threading.ThreadAbortException: 正在中止线程
在没有“Thread.Abort()”的情况下立即停止 C# 线程