4

这里有一个谜题给你们。我有一个多线程程序,其中一些线程使用锁和信号量等托管资源。某些锁定释放原语只能从完成锁定获取的同一线程执行。

所以这是我的难题:我包装了这些类型的操作:try { lock-acquire... do something } finally { lock-release },但有时当我的线程终止时,finally 子句由 .NET 垃圾收集线程执行,并且不是我的线程。(具体情况实际上涉及到在“using”语句中分配的对象的Dispose;详情见下文)

演示有点棘手;我一直在 Isis2 中看到它,我通过检查获取和完成块中的线程 ID 发现这是发生的。但是我没有适合你的 3 行演示,对此我很抱歉。我知道这会使提供帮助变得更容易。

有没有一种方法可以延迟线程的终止,直到与该线程关联的所有挂起的 finalize 块都已执行?

---- 为 Mark 添加了详细信息 ----

我真正在做的是一个相当复杂的自我检测锁定包,它具有各种具有线程优先级的锁定抽象(有界缓冲区、障碍、普通锁等),旨在自我检测死锁、优先级反转、非常慢的锁定授予和其他问题。我的代码足够复杂并且线程足够多,因此我需要它来调试它。

我的基本构造风格的一个例子是:

LockObject  myLock = new LockObject("AnIsis2Lock");

...

using(new LockAndElevate(myLock)) { stuff }

LockObject 是一个自我监控锁...... LockAndElevate 会记录我锁定它(在这种情况下),然后在我解锁它时跟踪处置。因此,我正在利用这样一个事实,即 using 应该在块完成时处理新对象——即使它抛出异常。

我看到的是,线程经常终止,但使用 dispose 事件实际上并没有发生;他们稍后在其他线程上运行。这只发生在我的一个线程终止时;在正常执行中,整个事情就像一个魅力。

因此,由于使用 translates 来尝试... finally,我的问题是根据 finally 子句发布的。

4

1 回答 1

2

所以到目前为止,这是我对自己问题的最佳回答,主要基于调试 Isis2 行为的经验。

如果线程没有终止,“using(x = new something()) { }”(映射到“try { x= new something(); ...} finally { x.dispose }”)就像你一样工作期望:退出代码块时发生处置。

但是异常会破坏控制流。因此,如果您的线程抛出 IsisException,或者“某事”中的某事发生了这种情况,则控制权将传递给该异常的 catch。这是我正在处理的情况,我的 catch 处理程序在堆栈上更高。C#/.NET 面临一个选择:是先捕获 IsisException,还是先进行 dispose?在这种情况下,我相当肯定系统首先会系统地执行 IsisException。结果,分配对象“x”的终结器尚未执行,但可以运行,需要尽快调用。

[注意:对于那些好奇的人,Using 语句以调用 Dispose 结束,但根据文档,推荐的行为是有一个终结器 ~something() { this.Dispose; } 只是为了涵盖所有可能的代码路径。然后可能会多次调用 Dispose,您应该保留一个标志,锁定并发,并仅在第一次调用 Dispose 时释放您的托管资源。]

现在的关键问题是终结器显然可能不会在你的线程有机会终止之前运行,在这种情况下捕获的异常会终止你的线程;如果没有,C# 将通过在 GC 终结器线程上调用 dispose 来释放对象。因此,如果在我的代码中,x.Dispose() 解锁某些东西可能会发生错误:锁获取/释放必须发生在 .NET 上的同一线程中。所以这对你和我来说都是潜在错误的来源!在这种情况下,在我的异常处理程序中调用 GC.WaitForFinalizers 似乎有帮助。对我来说不太清楚的是,这是否能真正保证坏事不会发生。

我自己的代码中的另一个严重错误是我错误地在几个线程中捕获了 ThreadAbortException,这是由于对这些线程如何工作的古老误解。不要这样做。我现在可以看到它给 .NET 带来了严重的问题。永远不要使用 Thread.Abort。

所以在这个理解的基础上,我修改了Isis2,它现在运行良好;当我的线程终止时,终结器似乎确实运行正常,dispose 似乎确实发生在线程退出之前(因此在它的 id 可以重用之前,这让我感到困惑),一切对世界都有好处。那些使用线程、优先级和自我管理的锁/屏障/有界缓冲区和信号量的人应该小心:这里潜伏着巨龙!

于 2012-07-21T11:38:49.477 回答