确保线程终止时“finally”在同一线程上运行

时间:2012-07-18 21:27:57

标签: c# finalize

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

所以这是我的谜题:我包装了这些类型的操作:尝试{lock-acquire ...做一些} finally {lock-release},但有时当我的线程终止时,finally子句由.NET垃圾执行收集线程,而不是我的线程。 (具体情况实际上涉及在“使用”语句中分配对象的处理;有关详细信息,请参见下文)

这对演示来说有点棘手;我在Isis2中一直看到它,我发现通过检查acquire和finalize块中的thread-id会发生这种情况。但我没有为您提供3线演示,对此我很抱歉。我知道这会更容易提供帮助。

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

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

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

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

LockObject  myLock = new LockObject("AnIsis2Lock");

...

using(new LockAndElevate(myLock)) { stuff }

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

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

因此,自从使用翻译尝试...最后,我的问题是根据最终条款发布的。

1 个答案:

答案 0 :(得分:1)

截至目前,这是我对自己问题的最佳答案,主要基于调试Isis2行为的经验。

如果线程没有终止,“使用(x = new something()){}”(映射到“try {x = new something(); ...} finally {x.dispose}”)正如您所期望的那样:在退出代码块时发生处置。

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

[注意:对于那些好奇的人,Using语句以调用Dispose结束,但根据文档,推荐的行为是有一个终结者〜({。。Dispose;只是为了涵盖所有可能的代码路径。然后可以不止一次调用Dispose,你应该保留一个标志,锁定并发性,并且只在第一次调用Dispose时处理你的托管资源。]

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

我自己的代码中的另一个严重错误是我错误地在几个线程中捕获了ThreadAbortException,原因是对这些工作方式的误解。不要这样做。我现在可以看到它会导致.NET出现严重问题。只是不要使用Thread.Abort。

所以在这种理解的基础上,我修改了Isis2,它现在运行得很好;当我的线程终止时终结器确实看起来运行正常,在线程退出之前似乎发生了dispose(因此在它的id被重用之前,这导致了我的混乱),并且一切都对世界有益。那些使用线程,优先级和自我管理的锁/障碍/有界缓冲区和信号量的人应该谨慎:潜伏在这里的龙!