我从我读过的关于该主题的大量文章中了解到 Thread.Abort() 是邪恶的,所以我目前正在删除我的 abort's 以便以更清洁的方式替换它;在比较了 stackoverflow 上人们的用户策略之后,然后阅读了 "How to: Create and Terminate Threads (C# Programming Guide) "来自 MSDN,两者都说明了一种非常相同的方法——即使用 volatile bool
方法检查策略,这很好,但我还有一些问题......
对我来说,最突出的一点是,如果您没有一个简单的工作进程,它只是运行一个循环处理代码怎么办?例如对我来说,我的进程是一个后台文件上传进程,我实际上循环遍历每个文件,所以这就是我可以添加我的 while (!_shouldStop)
在覆盖我每次循环迭代的顶部,但我有更多的业务流程发生在它到达下一次循环迭代之前,我希望这个取消过程快速;不要告诉我我需要在整个工作函数中每 4-5 行向下撒一次这些 while 循环?!
我真的希望有更好的方法,有人可以告诉我这是否确实是正确的[且唯一的?] 方法,或者他们过去使用的策略来实现我所追求的目标。
谢谢帮。
进一步阅读:All these SO responses假设工作线程将循环。这对我来说并不舒服。如果是线性但及时的后台操作呢?
最佳答案
不幸的是,可能没有更好的选择。这实际上取决于您的具体情况。这个想法是在安全点优雅地停止线程。这就是为什么Thread.Abort
的关键所在不好;因为它不能保证发生在安全点。通过在代码中添加停止机制,您可以有效地手动定义安全点。这称为合作取消。基本上有 4 种广泛的机制可以做到这一点。您可以选择最适合您情况的一种。
轮询停止标志
你已经提到了这个方法。这是一个很常见的。在算法中的安全点定期检查标志,并在收到信号时退出。标准方法是标记变量 volatile
.如果这是不可能的或不方便的,那么您可以使用 lock
.请记住,您不能将局部变量标记为 volatile
因此,例如,如果 lambda 表达式通过闭包捕获它,那么您将不得不采用不同的方法来创建所需的内存屏障。对于这种方法,无需多说。
使用 TPL 中的新取消机制
这类似于轮询停止标志,不同之处在于它使用 TPL 中的新取消数据结构。它仍然基于合作取消模式。你需要得到一个 CancellationToken
和定期检查IsCancellationRequested
.要请求取消,您可以调用 Cancel
在 CancellationTokenSource
最初提供 token 的那个。您可以使用新的取消机制做很多事情。您可以阅读更多关于 here .
使用等待句柄
如果您的工作线程在正常操作期间需要等待特定时间间隔或信号,则此方法很有用。您可以 Set
ManualResetEvent
,例如,让线程知道是时候停止了。您可以使用 WaitOne
测试事件返回 bool
的函数指示事件是否已发出信号。 WaitOne
接受一个参数,该参数指定如果在该时间内未发出事件信号,则等待调用返回的时间。您可以使用此技术代替 Thread.Sleep
并同时得到停止指示。如果还有其他 WaitHandle
也很有用线程可能必须等待的实例。您可以拨打WaitHandle.WaitAny
在一次调用中等待任何事件(包括停止事件)。使用事件比调用 Thread.Interrupt
更好因为您可以更好地控制程序流程(Thread.Interrupt
抛出异常,因此您必须策略性地放置 try-catch
块以执行任何必要的清理)。
专业场景
有几种一次性方案具有非常专业的停止机制。将它们全部列举出来绝对超出了这个答案的范围(更不用说这几乎是不可能的)。我在这里的意思的一个很好的例子是 Socket
类(class)。如果线程在调用 Send
时被阻塞或 Receive
然后拨打 Close
将在有效解除阻塞的任何阻塞调用上中断套接字。我确信 BCL 中还有其他几个区域可以使用类似的技术来解除线程阻塞。
通过 Thread.Interrupt
中断线程
这里的优点是它很简单,您不必真正专注于在代码中添加任何东西。缺点是您几乎无法控制算法中安全点的位置。原因是因为Thread.Interrupt
通过在 jar 装 BCL 阻塞调用之一中注入(inject)异常来工作。其中包括 Thread.Sleep
, WaitHandle.WaitOne
, Thread.Join
等。所以你必须明智地放置它们。然而,大多数时候算法决定了它们去哪里,这通常很好,特别是如果你的算法大部分时间都花在这些阻塞调用之一上。如果您的算法不使用 BCL 中的阻塞调用之一,则此方法对您不起作用。这里的理论是ThreadInterruptException
仅从 .NET 等待调用生成,因此它可能处于安全点。至少您知道该线程不能处于非托管代码中,也不能退出临界区而使悬空锁处于已获取状态。尽管这比 Thread.Abort
侵入性小我仍然不鼓励使用它,因为哪个调用响应它并不明显,而且许多开发人员将不熟悉它的细微差别。
关于c# - 关于在 .NET 中干净地终止线程的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3632149/