C# - 线程中止异常(Thread Abort Exception)重新抛出自身

12

我有下面的代码:

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;
    }

4
不要使用"throw ex",只需使用"throw"即可。前者会重置堆栈跟踪。 - Bryan Boettcher
你推理的基本缺陷在于你期望程序员总是为TAE编写catch子句。当然他们不会这样做。这并不是因为他们不希望被这样对待。 - Hans Passant
4个回答

7

Thread.ResetAbort() 不是常规使用的方法。如果您不了解线程中止的原因,则可能会导致不良行为。因此,为了使ASP.NET在共享托管环境中更加稳定,需要 SecurityPermissionFlag.ControlThread 权限来调用 Thread.ResetAbort()

MSDN 链接


1
此外,如果线程终止时间过长,则可能会发生粗鲁的线程终止,它不会注意到“ResetAbort”。 - Mark Avenius
另外,如果您执行太多的中止操作(就像在某种无限循环中所做的那样),IIS 环境将会认为您是一个糟糕的程序并关闭您。这可能是您看到的“虚假”线程中止的原因。 - user3624833

6
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是用于非常罕见的情况,即您明确检测到线程中止,并且您确切知道为什么发生它,并且想要防止它发生。
不用说,这种用例极其罕见。运行时以非常特殊的方式处理线程中止,应尽可能避免使用它们。它们甚至如你所指出的那样不可靠,而且所有这些讨论都忽略了CER,这使得事情变得更糟。

2
因为终止线程并不一定意味着会抛出异常。对于“终止过程”,catch (ThreadAbortException) 块只是另一个关键代码区域。它只提供了一种线程安全且方便的方式来检测当前线程是否正在被终止(可能还传递了一些状态),以防我们想要执行一些特殊操作。除此之外,它就像任何其他关键代码区域(比如 finally 块)一样,在执行后将终止线程。
与此同时,在您的示例中,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 调用比有用更加麻烦。

2

重点是定义一个默认行为,即重新抛出异常,因为用户很有可能没有任何继续线程的理由。

此外,ResetAbort具有安全性要求,不能被任何代码调用。


我不是那个给你点踩的人,但是你原来的回答与现在有很大不同。 - David Crowell
@DavidCrowell 第二行代码丢失了。 - Rotem

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接