有关在.NET中干净地终止线程的问题
我理解Thread.Abort()是从我阅读过的主题中的大量文章中得到的,所以我现在正在为了取代它而更换一个更干净的方法。 并在比较用户的策略从这里的人在stackoverflow,然后在阅读“ 如何:创build和终止线程(C#编程指南) ”从MSDN两种说法非常相似 – 这是使用volatile bool
方法检查策略,这是很好的,但我仍然有几个问题….
在这里突出的是什么,如果你没有一个简单的工作stream程,只是运行一个循环的代码? 例如,对于我来说,我的过程是一个后台file upload过程,我实际上遍历每个文件,所以这是一些东西,并确保我可以添加我的while (!_shouldStop)
顶部覆盖我每个循环迭代,但我有更多的业务stream程发生在它下一个循环迭代之前,我想这个取消过程是活泼的; 不要告诉我,我需要在整个工作人员的function中,每隔4-5行撒上这些循环。
我真的希望有一个更好的办法,有人请告诉我,这是否是正确的[而且只有?]方法来做到这一点,或者他们以前用来实现我所追求的策略。
感谢帮派。
进一步阅读: 所有这些SO响应假定工作者线程将循环。 那不适合我。 如果这是一个线性的,但及时的背景操作呢?
不幸的是,可能没有更好的select。 这真的取决于你的具体情况。 这个想法是安全地停止线程的优雅。 这是Thread.Abort
不好的原因; 因为它不能保证在安全的地方发生。 通过使用停止机制喷洒代码,您可以有效地手动定义安全点。 这就是所谓的合作取消。 基本上有4个广泛的机制来做到这一点。 你可以select最适合你的情况。
投票停止标志
你已经提到了这个方法。 这是一个很常见的。 定期检查algorithm中安全点的标志,并在发出信号时进行保护。 标准的方法是标记variablesvolatile
。 如果这不可能或不方便,那么你可以使用一个lock
。 请记住,不能将局部variables标记为volatile
因此如果lambdaexpression式通过闭包来捕获它,那么您将不得不求助于不同的方法来创build所需的内存屏障。 这个方法没有什么需要说的。
在TPL中使用新的取消机制
这与轮询停止标志类似,只不过它使用TPL中新的取消数据结构。 它仍然基于合作取消模式。 您需要获取CancellationToken
并定期检查IsCancellationRequested
。 要请求取消,您可以在最初提供令牌的CancellationTokenSource
上调用Cancel
。 新的取消机制可以做很多事情。 你可以阅读更多关于这里 。
使用等待句柄
如果您的工作线程需要等待特定的时间间隔或正常操作期间的信号,则此方法可能非常有用。 例如,您可以Set
一个ManualResetEvent
,让线程知道该停止了。 您可以使用WaitOne
函数testing事件,该函数返回一个bool
指示事件是否被发送。 WaitOne
接受一个参数,该参数指定如果在该时间内事件未被发信号,等待该呼叫返回多less时间。 您可以使用此技术代替Thread.Sleep
并同时获取停止指示。 如果线程可能不得不等待的其他WaitHandle
实例也是有用的。 您可以调用WaitHandle.WaitAny
在任何一个事件(包括停止事件)中等待所有事件。 使用事件可以比调用Thread.Interrupt
更好,因为您可以更好地控制程序stream( Thread.Interrupt
引发exception,所以您必须策略性地放置try-catch
块来执行任何必要的清理)。
专业场景
有几个一次性场景具有非常专业的停止机制。 列举这些答案绝对不在这个答案的范围之内(不要介意这几乎是不可能的)。 我的意思是一个很好的例子是Socket
类。 如果线程在Send
或Receive
调用上被阻塞,那么调用Close
将会中断套接字,无论哪个阻塞调用是有效解除阻塞的。 我相信BCL还有其他几个领域可以使用类似的技术来解锁一个线程。
通过Thread.Interrupt
中断线程
这样做的好处是它很简单,而且你不必专注于在任何东西上喷洒你的代码。 缺点是你几乎无法控制algorithm中安全点的位置。 原因是因为Thread.Interrupt
通过在一个封闭的BCL阻塞调用中注入一个exception来工作。 这些包括Thread.Sleep
, WaitHandle.WaitOne
, Thread.Join
等。所以你必须明智的放置它们。 然而,大部分时间algorithm决定他们去哪里,通常很好,尤其是如果你的algorithm大部分时间花在这些阻塞调用之一。 如果你的algorithm没有使用BCL中的一个阻塞调用,那么这个方法对你不起作用。 这里的理论是, ThreadInterruptException
只是从.NET等待调用生成的,所以它可能在一个安全的地方。 至less你知道这个线程不能处于非托pipe代码中,或者在一个临界区保留一个处于获取状态的悬挂锁。 尽pipe这比Thread.Abort
侵入性更小,我还是不鼓励它的使用,因为不清楚哪个调用会响应它,而且很多开发人员会不了解它的细微差别。
那么,不幸的是,在multithreading中,你经常不得不为了清洁而妥协“快乐”……如果你Interrupt
它,你可以立即退出一个线程,但是它不会很干净。 所以不行,你不必每隔4-5行撒上_shouldStop
检查,但是如果你打断了你的线程,那么你应该处理这个exception并以干净的方式退出循环。
更新
即使它不是一个循环线程(也许它是一个执行一些长时间运行的asynchronous操作或input操作的某种types的块的线程),您可以Interrupt
它,但是仍然应该捕获ThreadInterruptedException
并干净地退出线程。 我认为你读过的例子是非常合适的。
更新2.0
是的,我有一个例子…我会告诉你一个基于你引用的链接的例子:
public class InterruptExample { private Thread t; private volatile boolean alive; public InterruptExample() { alive = false; t = new Thread(()=> { try { while (alive) { /* Do work. */ } } catch (ThreadInterruptedException exception) { /* Clean up. */ } }); t.IsBackground = true; } public void Start() { alive = true; t.Start(); } public void Kill(int timeout = 0) { // somebody tells you to stop the thread t.Interrupt(); // Optionally you can block the caller // by making them wait until the thread exits. // If they leave the default timeout, // then they will not wait at all t.Join(timeout); } }
如果取消是你正在构build的东西的一个要求,那么就应该像对待其他代码一样尊重 – 这可能是你必须devise的东西。
让我们假设你的线程一直在做两件事情之一。
- CPU绑定的东西
- 等待内核
如果你在有问题的线程中绑定CPU,那么你可能有一个很好的地方插入纾困检查。 如果你正在调用别人的代码来执行一些长时间运行CPU的任务,那么你可能需要修改外部代码,将其移出进程(中止线程是邪恶的,但中止进程是明确的和安全的)等等
如果你正在等内核,那么可能会有一个句柄(或者fd,或者mach端口等)参与等待。 通常,如果销毁相关的句柄,内核将立即返回一些失败代码。 如果你在.net / java /等。 你可能最终会有一个例外。 在C中,无论您已经具备哪些代码来处理系统调用失败,都会将错误传播到应用程序的有意义的部分。 无论哪种方式,你都可以非常及时地从低层的地方跳出来,而不需要在任何地方撒上新的代码。
我经常使用这种代码的策略是跟踪需要closures的句柄列表,然后让我的中止函数设置“取消”标志,然后closures它们。 当函数失败时,可以检查标志并报告由于取消而导致的失败,而不是由于具体的exception/错误号是什么。
您似乎意味着取消的可接受粒度在服务调用的级别。 这可能不是一个好的想法 – 同步取消后台工作并从前台线程join旧的后台线程会更好。 这是更清洁的因为:
-
它避免了一系列的竞争条件,当老的线程在意外延迟之后恢复生机。
-
它避免了挂起后台进程所造成的潜在的隐藏线程/内存泄漏,使得挂起后台线程的影响成为可能。
有两个理由害怕这种方法:
-
你不认为你可以及时中止自己的代码。 如果取消是您的应用程序的要求,那么您确实需要作出的决定是资源/业务决策:做一个黑客攻击,或干净地解决您的问题。
-
你不相信你打来的一些代码,因为这是你无法控制的。 如果你真的不信任它,可以考虑把它移出进程。 这样,你可以更好地与多种风险隔离,包括这个风险。
也许问题的一个方面是你有这么长的方法/ while循环。 无论你是否有线程问题,你应该把它分解成更小的处理步骤。 假设这些步骤是Alpha(),Bravo(),Charlie()和Delta()。
然后你可以做这样的事情:
public void MyBigBackgroundTask() { Action[] tasks = new Action[] { Alpha, Bravo, Charlie, Delta }; int workStepSize = 0; while (!_shouldStop) { tasks[workStepSize++](); workStepSize %= tasks.Length; }; }
所以是的,它无休止地循环,但检查是否是在每个业务步骤之间停止的时间。
无处不在的时候,你不必洒水。 外部while循环只是检查是否被告知停止,如果没有进行另一次迭代…
如果你有一个直接的“去做一些事情,并closures”线程(没有循环),那么你只需检查_shouldStop布尔之前或之后的每个主要斑点线程。 这样你就知道应该继续还是退出。
例如:
public void DoWork() { RunSomeBigMethod(); if (_shouldStop){ return; } RunSomeOtherBigMethod(); if (_shouldStop){ return; } //.... }
最好的答案很大程度上取决于你在线程中做了什么。
-
就像你说的,大多数答案围绕着每对夫妇轮询一个共享的布尔值。 即使你不喜欢它,这通常是最简单的scheme。 如果你想让自己的生活更轻松,你可以编写一个像ThrowIfCancelled()这样的方法,如果你完成的话,会抛出一些exception。 纯粹主义者会说这是(控制stream)exception的(喘气),但是再次说明cacelling是非常特殊的。
-
如果你正在做IO操作(比如networking事物),你可能要考虑使用asynchronous操作来做所有的事情。
-
如果您正在执行一系列步骤,则可以使用IEnumerable技巧创build一个状态机。 例:
<
abstract class StateMachine : IDisposable { public abstract IEnumerable<object> Main(); public virtual void Dispose() { /// ... override with free-ing code ... } bool wasCancelled; public bool Cancel() { // ... set wasCancelled using locking scheme of choice ... } public Thread Run() { var thread = new Thread(() => { try { if(wasCancelled) return; foreach(var x in Main()) { if(wasCancelled) return; } } finally { Dispose(); } }); thread.Start() } } class MyStateMachine : StateMachine { public override IEnumerabl<object> Main() { DoSomething(); yield return null; DoSomethingElse(); yield return null; } } // then call new MyStateMachine().Run() to run.
>
过度devise? 这取决于你使用多less个状态机。 如果你只有一个,是的。 如果你有100,那么也许不是。 太棘手? 那么,这取决于。 这种方法的另一个好处是,它可以让你(稍作修改)把你的操作移到Timer.tickcallback和void线程中,如果有意义的话。
并做一切blucz也说。
if (_shouldStop) CleanupAndExit();
添加一个类似if (_shouldStop) CleanupAndExit();
东西,而不是添加一个while循环if (_shouldStop) CleanupAndExit();
只要有意义就行。 每一次操作之后都不需要检查,或者将代码全部洒在上面。 相反,把每一张支票都当作一个退出线索的机会,并且在战略上加上他们的想法。
所有这些SO响应假定工作线程将循环。 那不适合我
代码需要很长时间没有太多的方法。 循环是一个非常重要的编程构造。 使代码花费很长时间而不循环需要大量的语句。 数十万。
或者调用一些正在为你循环的代码。 是的,很难让代码停止点播。 那只是不行。