我需要在C#中开发多线程Azure辅助角色 - 创建多个线程,向它们提供请求,每个请求可能需要很长时间才能处理(不是我的代码 - 我会调用COM对象来做实际工作)。
在角色关闭后,我需要优雅地停止处理。我怎么做?看起来如果我只是调用Thread.Abort()
,那么线程中会抛出ThreadAbortException
,线程甚至可以使用try-catch-finally
(或using
)来清理资源。这看起来非常可靠。
困扰我的是我的经验主要是C ++,并且不可能在非托管应用程序中优雅地中止线程 - 它将在没有任何进一步处理的情况下停止,这可能会使数据处于不一致状态。因此,如果我为忙碌的线程调用Thread.Abort()
,我是否会发生类似的事情。
将Thread.Abort()
与ThreadAbortException
一起使用是否安全?如果我这样做,我应该注意什么?
答案 0 :(得分:7)
在.NET安全实践中使用Thread.Abort()并处理ThreadAbortException吗?
TL; DR版本:不,不是。
通常,当所有类型不变量(无论是否明确说明)实际为真时,您都是安全的。然而,许多方法在运行时会破坏这些不变量,只有在它们最后再次为真时才会达到新的状态。如果线程在保持不变量的状态下处于空闲状态,那么你就可以了,但在这种情况下,最好使用类似事件的东西来指示线程正常退出(即你不需要中止)。
在一个线程中抛出带外异常 1 ,而这种不变量不是真的,即。无效,状态是问题开始的地方。这些问题包括但不限于相互不一致的字段和属性值(处于无效状态的数据结构),未退出的锁定以及表示“发生更改”的事件未被触发。
在许多情况下,可以在清理代码中处理这些内容(例如,finally
块),但是然后考虑在发生带外异常时会发生什么在清理代码?这导致清理代码的清理代码。但是那个代码是自我易受攻击的,所以你需要清理清理代码清理代码的代码...... 它永远不会结束!
有解决方案,但它们不易设计(并且往往会影响整个设计),甚至更难以测试 - 如何重新创建所有案例(想想组合爆炸)。两种可能的路线是:
处理状态副本,更新副本,然后以原子方式交换新状态的当前状态。如果存在带外异常,则原始状态仍然存在(并且终结者可以清理临时状态)。
这与数据库事务的功能非常相似(尽管RDBMS使用锁和事务日志文件)。
它也类似于实现C ++社区中为响应paper questioning if exceptions could ever be safe而开发的“强异常保证”的方法(C ++当然没有GC / finaliser队列来清理丢弃的对象)。请参阅Herb Sutters "Guru of the Week #8: CHALLENGE EDITION: Exception Safety"以获得解决方案。
在实践中,除非您的州可以封装在一个参考文献中,否则很难实现。
请看"Constrained Execution Regions",但不要考虑在这些情况下您可以做些什么的限制。 (MSDN杂志从.NET 2 beta期 2 获得introductory article(主题介绍,而非介绍级别)。
实际上,如果你必须这样做,使用方法#2来管理#1下的状态变化可能是最好的方法,但要正确,然后验证它是正确的(并保持正确性)是硬
总结:这有点像优化:规则1:不要这样做;规则2(仅限专家):除非您没有其他选择,否则不要这样做。
1 ThreadAbortException
不是唯一的例外。
2 所以细节可能已经改变了。
答案 1 :(得分:5)
中止线程有问题的一个例子。
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
的普通代码。
答案 2 :(得分:4)
正确处理TheadAbortException
非常困难,因为它可以在线程执行的任何代码的中间抛出。
大多数代码都是在假设某些操作(例如int i = 0;
)从不导致异常的情况下编写的,因此关键异常处理仅应用于实际上可能导致异常的代码。当您中止某个线程时,该异常可以出现在未准备好处理它的代码中。
最佳做法是告诉线程自行结束。为运行该线程的方法创建一个类,并在其中放置一个布尔变量。启动线程的代码和运行该线程的方法都可以访问该变量,因此您只需将其切换为告诉线程结束即可。当然,线程中的代码必须定期检查值。
答案 3 :(得分:4)
Thread.Abort
是一种不安全的杀死线程的方法。
ThreadAbortException
,这是一个可以捕获的特殊异常,但它会在catch块的末尾自动再次引发 最佳做法是使用支持工作取消的包装器,例如Task
类或使用volatile
bool。而不是Thread.Abort
考虑使用Thread.Join
来阻止调用线程,直到处理工作线程。
一些链接:
- How To Stop a Thread in .NET (and Why Thread.Abort is Evil)
- Managed code and asynchronous exception hardening
- The dangers of Thread.Abort
答案 4 :(得分:1)
正如其他人所提到的,中止线程可能不是一个好主意。但是,用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方法长时间运行,您可能只需要“咬紧牙关”并等待它完成当前的迭代。在当前的值迭代期间计算的信息是否为用户?
如果没有,我可以选择一个选项:
答案 5 :(得分:0)
让线程的方法返回是最好的做法:
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);
}
}
答案 6 :(得分:-1)
请从这里获得简单的想法,根据您的要求,检查thead isalive属性,然后中止你的线程........................... ..................................
ThreadStart th = new ThreadStart(CheckValue);
System.Threading.Thread th1 = new Thread(th);
if(taskStatusComleted)
{
if (th1.IsAlive)
{
th1.Abort();
}
}
private void CheckValue() { //我的方法.... }