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

本文关键字:Exception Abort 自己 新抛出 Thread 线程 异常 | 更新日期: 2023-09-27 18:09:29

我有当前代码:

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

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

Thread.ResetAbort()不适合一般使用。如果你不明白为什么会发生头部中止,就会导致不希望发生的行为。正因为如此,才有可能做出ASP。. net稳定在共享主机环境中,需要SecurityPermissionFlag.ControlThread权限才能调用Thread.ResetAbort()

MSDN链接

ThreadAbortException重新抛出自己的目的是确保线程终止,除非用户显式调用ResetAbort

让我解释一下:

try
{
    // ... Thread abort happens somewhere in here
}
catch (Exception ex)
{
    _log.Error(ex);
}

这里有一个典型的代码示例,它确保没有异常从try块内部传播。我知道捕获Exception是不好的做法,但是像这样的代码仍然存在。

如果你调用Abort,而线程在try块内,你仍然希望它中止。你不能指望用户到处写这种代码:

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,这使事情变得更糟。

关键是要定义一个默认行为,在这个行为中异常被重新抛出,看看用户有多大的机会继续这个线程。

并且ResetAbort有安全需求,不能被任何代码调用

因为终止线程并不一定意味着会抛出异常。对于中止过程catch (ThreadAbortException)块只是代码的另一个关键区域。它只是为我们提供了一种线程安全且方便的方法来检测当前线程是否被终止(可能还传递了一些状态),以防我们想做一些特殊的事情。除此之外,它就像任何其他关键区域(如finally块),在执行后它将终止线程。

同时,在您的示例中,Abort被同步地调用(这实际上是安全的),在这种情况下,非常类似于抛出异常。当从另一个线程异步调用时,事情才变得有趣和危险,因为Abort过程比仅仅抛出一个异常更复杂:本质上,首先,线程被标记为被终止,然后执行关键代码区域(例如finally块),只有在异常被抛出时,如果 abortrequest 标志仍然在线程上设置,等等。

下面的代码通过在不捕获任何异常的情况下恢复中断的线程来说明这一事实:

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。中止API(可能仍然是,我不知道)用于促进线程和AppDomain在ASP.NET等框架中的重用。

在Joe Duffy的博客文章《托管代码和异步异常强化》中,他谈到了ResetAbort和Abort API:

一些框架基础设施,最明显的是ASP。NET,甚至中止单个线程常规地不卸载域。它们支持ThreadAbortExceptions调用在线程上执行ResetAbort并重用它或将其返回到CLR ThreadPool。

我可以想象它可以在框架中重用托管线程,从而减少开销。然而,这个容易被误解的API在用户代码中引入的问题(糟糕的线程同步设计、糟糕的异常处理、死锁等等)使得Abort和ResetAbort调用更加麻烦而不是有用。