【问题标题】:Why isn't ThreadAbortException automatically rethrown if raised after an asynchronous await?为什么在异步等待后引发 ThreadAbortException 不会自动重新抛出?
【发布时间】:2018-06-22 03:01:35
【问题描述】:

在调查an issue with finally, await, and ThreadAbortException 时,我遇到了另一个怪癖。根据documentation

ThreadAbortException 是一个可以被捕获的特殊异常,但它会在 catch 块的末尾自动再次引发。

但是考虑一下这个控制台程序:

class Program
{
    static void Main()
    {
        Run(false).GetAwaiter().GetResult();
        Run(true).GetAwaiter().GetResult();
    }

    static async Task Run(bool yield)
    {
        Console.WriteLine(yield ? "With yielding" : "Without yielding");
        try
        {
            try { await Abort(yield); }
            catch (ThreadAbortException)
            {
                Console.WriteLine("    ThreadAbortException caught");
            } // <-- ThreadAbortException should be automatically rethrown here
        }
        catch (ThreadAbortException)
        {
            Console.WriteLine("    Rethrown ThreadAbortException caught");
            Thread.ResetAbort();
        }
    }

    static async Task Abort(bool yield)
    {
        if (yield)
            await Task.Yield();
        Thread.CurrentThread.Abort();
    }
}

当我在 Visual Studio 2015 中编译它时,输出是:

Without yielding
    ThreadAbortException caught
    Rethrown ThreadAbortException caught
With yielding
    ThreadAbortException caught

因此,Task.Yield() 之后引发的 ThreadAbortException 不再被 catch 块自动重新抛出!这是为什么呢?

【问题讨论】:

    标签: c# .net async-await try-catch threadabortexception


    【解决方案1】:

    如果你不await Task.Yield,会发生这种情况的原因是代码与调用者在同一个线程上同步执行,所以就像根本不是async

    当您await 时,延续将在ThreadPool 线程上排队,该线程是一个托管线程并且行为不同。

    由于在内部被捕获并从与当前线程不同的线程重新抛出,因此它不会在转换逻辑中保留其“特殊应用程序终止”异常的性质。

    此外,如果要重新抛出它,您甚至无法Thread.ResetAbort(),因为它适用于当前线程并且不会对实际中止的线程起作用。

    MSDN 文档也解释了这一点 here

    如果这些异常中的任何一个在由 公共语言运行时,异常终止线程,但 公共语言运行时不允许异常继续 进一步。

    如果这些异常在主线程中未处理,或者在线程中 从非托管代码进入运行时,它们正常进行, 导致申请终止。

    我对这背后的基本原理的猜测是:ThreadAbortException 被重新抛出,以避免顽固的线程在不应该的时候尝试保持活动状态,但让它流动并杀死其他不同的线程可能会很糟糕想法并导致意外行为。

    【讨论】:

    • 这些不会被视为已处理的异常吗?为什么不呢?
    • @JonathonChase 如果我理解你的问题(如果我们抓住它,为什么要重新抛出!?)我只能想象这是一个遗留行为,如果主线程是,它会杀死应用程序中止。上面的文章称他们为The common language runtime provides a backstop for certain unhandled exceptions that are used for controlling program flow,这并不完全清楚。
    • 对不起,我不清楚。您引用的文档讨论了未处理异常的行为。我的理解是这些异常是由catch语句处理的。
    • 你说得对,它称它们为unhandled 异常,但它没有解释为什么它在捕获它后仍处于未处理状态。这部分文档解释了 Remarks 部分中的特殊情况:msdn.microsoft.com/en-us/library/5b50fdsz(v=vs.110).aspx
    • 正如我在答案中提到的,我认为这是因为它在不同的线程上。重新抛出它是为了避免顽固的线程在不应该尝试保持活动状态并完全展开调用堆栈,但让它杀死其他线程可能会很糟糕。
    猜你喜欢
    • 2014-08-17
    • 2012-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-07
    • 2020-07-09
    • 1970-01-01
    • 2012-08-14
    相关资源
    最近更新 更多