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

我需要在C#中开发一个multithreading的Azure辅助角色 – 创建多个线程,向它们提供请求,每个请求可能需要一些很长的时间来处理(不是我的代码 – 我将调用COM对象来进行实际工作)。

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

困扰我的是我的经验主要是C ++,并且不可能在非托管应用程序中优雅地中止线程 – 它将在没有任何进一步处理的情况下停止,这可能会使数据处于不一致状态。 因此,如果我为一个繁忙的线程调用Thread.Abort() ,我是否会有类似的事情。

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

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

TL; DR版本:不,不是。

通常,当所有类型不变量(无论是否明确说明)实际为真时,您都是安全的。 然而,许多方法会在运行时打破这些不变量,只有在最后再次为真时才会达到新状态。 如果线程在保持不变量的状态下处于空闲状态,那么你就可以了,但在这种情况下,最好使用类似事件的信号来指示线程优雅地退出(即,你不需要中止)。

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

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

有解决方案,但它们不容易设计(并且往往会影响整个设计),甚至更难测试 – 如何重新创建所有案例(想想组合爆炸)。 两条可能的路线是:

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

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

    它也类似于实现C ++社区中开发的“强exception保证”的方法,以回应一篇论文质疑exception是否安全 (C ++当然没有GC / finaliser队列来清理丢弃的对象)。 请参阅Herb Sutters “本周大师#8:挑战版:exception安全”解决方案。

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

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

实际上,如果你必须这样做,使用方法#2来管理#1下的状态变化可能是最好的方法,但是正确的方法,然后validation它是正确的(并保持正确性) 很难

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


1 ThreadAbortException不是唯一的此类exception。

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

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

 using(var disposable=new ClassThatShouldBeDisposed()) { ... } 

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

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

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

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

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

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

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

正确处理TheadAbortException非常困难,因为它可以在线程执行的任何代码的中间抛出。

大多数代码是在假设某些动作的情况下编写的,例如int i = 0; 永远不会导致exception,因此关键exception处理仅适用于实际上可能导致exception的代码。 当您中止线程时,exception可以出现在未准备好处理它的代码中。

最佳做法是告诉线程自行结束。 为运行该线程的方法创建一个类,并在其中放置一个布尔变量。 启动线程的代码和运行该线程的方法都可以访问该变量,因此您只需切换它就可以告诉线程结束。 当然,线程中的代码必须定期检查值。

Thread.Abort是一种不安全的杀死线程的方法。

  1. 它引发异步ThreadAbortException ,这是一个可以捕获的特殊exception,但它会在catch块的末尾自动再次引发
  2. 它可以使状态损坏,并且您的应用程序变得不稳定
  3. TAE在另一个主题中被提出

最佳实践是使用支持取消工作的包装器,例如Task类或使用volatile bool。 而不是Thread.Abort考虑使用Thread.Join ,它将阻止调用线程,直到工作线程被处理掉。

一些链接:
– 如何在.NET中停止线程(以及为什么Thread.Abort是邪恶的)
– 托管代码和异步exception加固
– Thread.Abort的危险

正如其他人所提到的,中止一个post可能不是一个好主意。 但是,用bool通知一个线程停止也可能不起作用,因为我们无法保证bool的值将跨线程同步。

使用事件可能更好:

 class ThreadManager { private Thread thread = new Thread(new ThreadStart(CallCOMMethod)); private AutoResetEvent endThread = new AutoResetEvent(false); public ThreadManager() { thread.Start(); } public StopThread() { endThread.Set(); } private void CallCOMMethod() { while (!endThread.WaitOne()) { // Call COM method } } } 

由于COM方法长时间运行,您可能只需要“咬紧牙关”并等待它完成当前的迭代。 在当前迭代值期间为用户计算的信息是?

如果没有,我可以选择一个选项:

  • 让ThreadManager本身在来自UI的单独线程上运行,该线程相对经常地检查来自用户的停止通知。
  • 当用户请求停止长时间运行操作时,UI线程可以立即返回给用户。
  • ThreadManager等待长时间运行的COM操作以完成其当前迭​​代,然后抛弃结果。

让线程的方法返回是最好的做法:

 void Run() // thread entry function { while(true) { if(somecondition) // check for a terminating condition - usually "have I been disposed?" break; if(workExists) doWork(); Thread.Sleep(interval); } } 

请根据您的要求从这里得到简单的想法,检查thead isalive属性,然后中止你的线程………………………… ………………………….

  ThreadStart th = new ThreadStart(CheckValue); System.Threading.Thread th1 = new Thread(th); if(taskStatusComleted) { if (th1.IsAlive) { th1.Abort(); } } 

private void CheckValue(){//我的方法….}