在.NET安全实践中使用Thread.Abort()并处理ThreadAbortException?

sha*_*oth 7 .net c# concurrency multithreading azure

我需要开发在C#中的多线程Azure的辅助角色 - 创建多个线程,饲料请求给他们,每个请求可能需要一些很长的时间来处理(不是我的代码 - 我会打电话给一个COM对象做实际的工作).

在角色关闭时,我需要优雅地停止处理.我怎么做?看起来,如果我只是叫Thread.Abort()ThreadAbortException被扔在线程和线程甚至可以使用try-catch-finally(或using)清理资源.这看起来非常可靠.

让我困扰的是,我的经验主要是C++,它是不可能的,优雅地中止一个线程在非托管应用程序-它只是停止没有任何进一步的处理,这可能不一致的状态留下的数据.因此,如果我要求Thread.Abort()一个繁忙的线程,我是否会发生类似的事情.

Thread.Abort()与处理一起使用是否安全ThreadAbortException?如果我这样做,我应该注意什么?

Ric*_*ard 7

在.NET安全实践中使用Thread.Abort()并处理ThreadAbortException?

TL; DR版本:不,不是.

通常,当所有类型不变量(无论是否明确说明)实际为真时,您都是安全的.然而,许多方法在运行时会破坏这些不变量,只有在它们最后再次为真时才会达到新的状态.如果线程与持有你会没事的不变量的状态空闲,但在这种情况下,最好使用类似的事件信号线程正常退出(即你不需要中止).

在这样的不变量 - 不是真的情况下,线程中抛出的带外异常1,即.无效,状态是问题开始的地方.这些问题包括但不限于相互不一致的字段和属性值(处于无效状态的数据结构),未退出的锁定以及表示未发生的"更改发生"的事件.

在许多情况下,可以在清理代码(例如finally块)中处理这些代码,但是考虑在清理代码中发生带外异常时会发生什么?这导致清理代码的清理代码.但是那个代码是自我易受攻击的,所以你需要清理代码清理代码的清理代码...... 它永远不会结束!

有解决方案,但他们不容易设计(而且往往影响你的整个设计),甚至难以测试如何重新创建所有的情况下(考虑组合爆炸).两条可能的路线是:

  1. 处理状态副本,更新副本,然后以原子方式交换新状态的当前状态.如果存在带外异常,则原始状态仍然存在(并且终结者可以清除临时状态).

    这与数据库事务的功能非常相似(尽管RDBMS使用锁和事务日志文件).

    这也是类似的方法来实现"强异常保证"在C++社区响应发达纸质疑,如果异常可能永远是安全的(C++的课程没有GC/finaliser队列清理废弃的对象).请参阅Herb Sutters "本周大师#8:挑战版:异常安全"解决方案.

    在实践中,除非您的状态可以封装在单个引用中,否则很难实现.

  2. 查看"约束执行区域",但不限制在这些情况下可以执行的操作的限制.(MSDN杂志有一篇介绍性文章(主题介绍,不是介绍级别),来自.NET 2 beta期2).

在实践中,如果你要做到这一点,使用方法#2管理下的#状态变化1,可能是最好的办法,但得到它的权利,并验证它是正确的(和正确性维护)是很难.

总结:这有点像优化:规则1:不要这样做; 规则2(仅限专家):除非您没有其他选择,否则不要这样做.


1 A ThreadAbortException不是唯一的例外.

2因此细节可能已经改变.


Cod*_*aos 5

中止线程有问题的一个例子.

using(var disposable=new ClassThatShouldBeDisposed())
{
   ...
}
Run Code Online (Sandbox Code Playgroud)

现在线程堕胎发生在类的构造函数完成之后但在赋值给局部变量之前.所以它不会被处理掉.最终终结器将会运行,但这可能会更晚.

确定性处置和线程流产不能很好地协同工作.我知道在使用线程中断时获得安全处理的唯一方法是将所有关键代码放在finally子句中.

try
{//Empty try block
}
finally
{
  //put all your code in the finally clause to fool thread abortion
  using(var disposable=new ClassThatShouldBeDisposed())
  {
  ...
  }
}
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为线程中止允许finally代码执行.当然这意味着线程堕胎在代码离开finally块之前根本不起作用.

使用线程中止使代码正常工作的一种方法是使用以下代码而不是using语句.不幸的是,这非常难看.

ClassThatShouldBeDisposed disposable=null;
try
{
  try{}finally{disposable=new ClassThatShouldBeDisposed();}
  //Do your work here
}
finally
{
  if(disposable!=null)
    disposable.Dispose();
}
Run Code Online (Sandbox Code Playgroud)

我个人认为线程永远不会被中止(除非卸载AppDomain),因此编写using基于常规的代码.