【问题标题】:Why ThreadAbortException does not throw in catch block为什么 ThreadAbortException 不抛出 catch 块
【发布时间】:2011-10-03 12:10:50
【问题描述】:

假设我有这个代码:

    static void Main(string[] args)
    {
        var thread = new Thread(() =>
        {
            try
            {
                throw new InvalidOperationException();
            }
            catch (Exception)
            {
                Thread.Sleep(Timeout.Infinite);
            }
        });
        thread.Start();

        Thread.Sleep(TimeSpan.FromSeconds(1));

        thread.Abort();
        thread.Join();
    }

它启动线程,然后线程在 catch 块中进入睡眠状态,之后我们尝试中止线程。

Abort 方法必须引发 ThreadAbortException。但是在 catch 块中它不会发生。 我是documented

如果正在调用 Abort 的线程可能会阻塞 aborted 位于代码的受保护区域中,例如 catch 块, finally 块,或受约束的执行区域。如果那个线程 调用 Abort 持有中止线程所需的锁,即死锁 可能发生。

我的问题是为什么。为什么它会这样工作?因为在 catch 块中,我们可以引发任何异常并且所有工作都必须这样做。

更新: 来自 Jordão 的 link。接受,因为这是最容易理解的说明。

受约束的执行区域 .NET Framework 2.0 引入了 受约束的执行区域 (CER),对 运行时和开发人员。在标记为 CER 的代码区域中, 运行时受限于抛出某些异步 会阻止该区域在其 整体。开发人员也受限于可以采取的行动 在该地区进行。这创建了一个框架和一个执行 编写可靠托管代码的机制,使其成为关键参与者 在 .NET Framework 2.0 的可靠性故事中。对于运行时 为了减轻负担,它为 CER 提供了两种便利。首先, 运行时将延迟在 CER 中执行的代码的线程中止。 换句话说,如果一个线程调用 Thread.Abort 来中止另一个线程 当前正在 CER 中执行的,运行时将不会中止 目标线程直到执行离开 CER。二、 运行时将尽快准备 CER,以避免 内存不足的情况。这意味着运行时会做 它通常会在代码区域期间执行的所有操作 JIT 编译。它还将探测一定数量的空闲堆栈 帮助消除堆栈溢出异常的空间。通过做这项工作 预先,运行时可以更好地避免可能发生的异常 区域内,防止资源被清理 适当地。为了有效地使用 CER,开发人员应避免 某些可能导致异步异常的操作。编码 受限于执行某些操作,包括诸如 显式分配、装箱、虚拟方法调用(除非目标 的虚方法调用已经准备好了),方法调用 通过反射,使用 Monitor.Enter(或 C# 中的 lock 关键字 和 Visual Basic® 中的 SyncLock)、isinst 和 castclass 指令 COM 对象,通过透明代理访问字段,序列化, 和多维数组访问。简而言之,CER 是一种移动方式 从您的代码到某个时间的任何运行时引发的故障点 在代码运行之前(在 JIT 编译的情况下),或者在代码之后 完成(用于线程中止)。然而,CERs 确实限制了 你可以写的代码。诸如不允许大多数分配的限制 或对未准备目标的虚拟方法调用很重要, 这意味着创作它们的开发成本很高。这意味着 CER 不适合大量通用代码,而且它们 相反,应该将其视为一种保证执行的技术 小代码区域。

【问题讨论】:

  • 这对我来说似乎是一个错误,Abort() 只是被忽略了。将此发布到 connect.microsoft.com

标签: c# multithreading try-catch


【解决方案1】:

问题是您尝试中止的线程在catch 子句中运行。

这将中止线程:

static void Main(string[] args) {
  var thread = new Thread(() => {
    Thread.Sleep(Timeout.Infinite);
  });
  thread.Start();

  Thread.Sleep(TimeSpan.FromSeconds(1));

  thread.Abort();
  thread.Join();
}

来自this article

在 .NET Framework 2.0 中,CLR 在 CER、finally 块、catch 块、静态构造函数和非托管代码上默认延迟正常线程中止。

此功能的存在是为了让 .NET 框架在面对某些异步异常时更加可靠。阅读我链接的文章以获得完整的故事。

您的代码基本上行为不端,主机可能会将该线程升级为粗鲁的线程中止:

CLR 主机使用粗鲁的线程中止和粗鲁的应用程序域卸载来确保可以控制失控的代码。当然,由于这些操作而无法运行终结器或非 CER finally 块会给 CLR 主机带来新的可靠性问题,因为这些操作很可能会泄漏回退代码应该清理的资源。

【讨论】:

    【解决方案2】:

    这是设计使然,这是在 Fx 3 或 4 中引入的。
    您可以通过自己的链接查找不同的版本并找到不同的描述。

    在这些受保护区域(如在 Fx 1.x 中)允许 AbortException 可能导致非常不可预测的情况和不稳定的进程。

    请注意,Thread.Abort() 通常是(曾经)不建议使用的。任何 catch 或 finally 子句中的长时间运行代码也是如此。

    不允许 Abort 中断 catch 子句解决了 Abort 的一些问题。但它仍然不完美。

    【讨论】:

      【解决方案3】:

      怀疑关键是当你陷入困境或最终阻塞时,你可能已经在尝试清理自己了。如果此时可以触发异步异常,则很难进行任何可靠的清理。

      Joe Duffy 的 blog post about asynchronous exceptions 可能比我更能澄清这一点......

      【讨论】:

      • 它在 finally 块中工作,即 Abort() 调用块。不在 catch 块中,这就是错误。
      • @Hans 它只是说它可能阻塞。当异常处于活动状态时,finally 块中可能会出现与 catch 块中相同的问题。
      • 此链接已失效。
      • @JamesBlake:修复了链接。
      猜你喜欢
      • 2012-01-04
      • 2023-03-21
      • 2015-03-05
      • 2016-09-01
      • 1970-01-01
      • 2018-02-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多