关于在 .NET 中干净地终止线程的问题 [英] Question about terminating a thread cleanly in .NET

查看:43
本文介绍了关于在 .NET 中干净地终止线程的问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我从我读过的关于该主题的大量文章中了解到 Thread.Abort() 是邪恶的,所以我目前正在删除所有中止的内容,以便以更简洁的方式替换它;在比较了 stackoverflow 上人们的用户策略之后,然后阅读了 "如何:创建和终止线程(C# 编程指南)" 来自 MSDN,两者都说明了一种非常相同的方法——即使用 volatile bool 方法检查策略,很好,但我还有一些问题......

I understand Thread.Abort() is evil from the multitude of articles I've read on the topic, so I'm currently in the process of ripping out all of my abort's in order to replace it for a cleaner way; and after comparing user strategies from people here on stackoverflow and then after reading "How to: Create and Terminate Threads (C# Programming Guide)" from MSDN both which state an approach very much the same -- which is to use a volatile bool approach checking strategy, which is nice, but I still have a few questions....

立即对我来说最突出的是,如果您没有一个简单的工作进程,它只是运行一个处理代码的循环呢?例如对我来说,我的进程是一个后台文件上传进程,我实际上循环遍历每个文件,所以这就是一些东西,并且确定我可以在顶部添加我的 while (!_shouldStop)我每次循环迭代,但我有更多的业务流程发生在它到达下一次循环迭代之前,我希望这个取消过程快速;不要告诉我我需要在整个工作函数中每 4 到 5 行撒一次这些 while 循环?!

Immediately what stands out to me here, is what if you do not have a simple worker process which is just running a loop of crunching code? For instance for me, my process is a background file uploader process, I do in fact loop through each file, so that's something, and sure I could add my while (!_shouldStop) at the top which covers me every loop iteration, but I have many more business processes which occur before it hits it's next loop iteration, I want this cancel procedure to be snappy; don't tell me I need to sprinkle these while loops every 4-5 lines down throughout my entire worker function?!

我真的希望有更好的方法,有人可以告诉我这是否确实是正确的[且唯一的]方法,或者他们过去使用的策略来实现我所追求的目标.

I really hope there is a better way, could somebody please advise me on if this is in fact, the correct [and only?] approach to do this, or strategies they have used in the past to achieve what I am after.

谢谢大家.

进一步阅读:所有这些 SO 响应都假设工作线程将循环.这对我来说并不舒服.如果是线性但及时的后台操作呢?

Further reading: All these SO responses assume the worker thread will loop. That doesn't sit comfortably with me. What if it is a linear, but timely background operation?

推荐答案

很遗憾,可能没有更好的选择.这实际上取决于您的具体情况.这个想法是在安全点优雅地停止线程.这就是Thread.Abort不好的症结所在;因为它不能保证发生在安全点.通过在代码中添加停止机制,您可以有效地手动定义安全点.这称为合作取消.基本上有 4 种广泛的机制可以做到这一点.您可以选择最适合您情况的一种.

Unfortunately there may not be a better option. It really depends on your specific scenario. The idea is to stop the thread gracefully at safe points. That is the crux of the reason why Thread.Abort is not good; because it is not guaranteed to occur at safe points. By sprinkling the code with a stopping mechanism you are effectively manually defining the safe points. This is called cooperative cancellation. There are basically 4 broad mechanisms for doing this. You can choose the one that best fits your situation.

轮询停止标志

您已经提到了这种方法.这是一个很常见的.在算法中的安全点定期检查标志,并在收到信号时退出.标准方法是标记变量volatile.如果这是不可能的或不方便的,那么您可以使用 lock.请记住,您不能将局部变量标记为 volatile,因此如果 lambda 表达式通过闭包捕获它,例如,那么您将不得不求助于不同的方法来创建所需的内存屏障.对于这种方法,无需多说.

You have already mentioned this method. This a pretty common one. Make periodic checks of the flag at safe points in your algorithm and bail out when it gets signalled. The standard approach is to mark the variable volatile. If that is not possible or inconvenient then you can use a lock. Remember, you cannot mark a local variable as volatile so if a lambda expression captures it through a closure, for example, then you would have to resort to a different method for creating the memory barrier that is required. There is not a whole lot else that needs to be said for this method.

在 TPL 中使用新的取消机制

这类似于轮询停止标志,不同之处在于它使用 TPL 中的新取消数据结构.它仍然基于合作取消模式.您需要获取 CancellationToken 并定期检查 IsCancellationRequested.要请求取消,您可以在最初提供令牌的 CancellationTokenSource 上调用 Cancel.您可以使用新的取消机制做很多事情.您可以在此处阅读更多信息.

This is similar to polling a stopping flag except that it uses the new cancellation data structures in the TPL. It is still based on cooperative cancellation patterns. You need to get a CancellationToken and the periodically check IsCancellationRequested. To request cancellation you would call Cancel on the CancellationTokenSource that originally provided the token. There is a lot you can do with the new cancellation mechanisms. You can read more about here.

使用等待句柄

如果您的工作线程在正常操作期间需要等待特定时间间隔或信号,则此方法很有用.例如,您可以Set一个ManualResetEvent,让线程知道该停止了.您可以使用 WaitOne 函数测试该事件,该函数返回一个 bool 指示该事件是否已发出信号.WaitOne 接受一个参数,该参数指定如果在该时间内未发出事件信号,则等待调用返回的时间.您可以使用此技术代替 Thread.Sleep 并同时获得停止指示.如果线程可能必须等待其他 WaitHandle 实例,它也很有用.您可以调用 WaitHandle.WaitAny 以在一次调用中等待任何事件(包括停止事件).使用事件比调用 Thread.Interrupt 更好,因为您可以更好地控制程序流程(Thread.Interrupt 抛出异常,因此您必须有策略地放置 try-catch 块以执行任何必要的清理).

This method can be useful if your worker thread requires waiting on an specific interval or for a signal during its normal operation. You can Set a ManualResetEvent, for example, to let the thread know it is time to stop. You can test the event using the WaitOne function which returns a bool indicating whether the event was signalled. The WaitOne takes a parameter that specifies how much time to wait for the call to return if the event was not signaled in that amount of time. You can use this technique in place of Thread.Sleep and get the stopping indication at the same time. It is also useful if there are other WaitHandle instances that the thread may have to wait on. You can call WaitHandle.WaitAny to wait on any event (including the stop event) all in one call. Using an event can be better than calling Thread.Interrupt since you have more control over of the flow of the program (Thread.Interrupt throws an exception so you would have to strategically place the try-catch blocks to perform any necessary cleanup).

特殊场景

有几种一次性方案具有非常专业的停止机制.将它们全部列举出来绝对超出了这个答案的范围(更不用说这几乎是不可能的).我在这里的意思的一个很好的例子是 Socket 类.如果线程在调用 SendReceive 时被阻塞,则调用 Close 将在任何阻塞调用上中断套接字,从而有效地解除阻塞.我确信 BCL 中还有其他几个领域可以使用类似的技术来解除线程阻塞.

There are several one-off scenarios that have very specialized stopping mechanisms. It is definitely outside the scope of this answer to enumerate them all (never mind that it would be nearly impossible). A good example of what I mean here is the Socket class. If the thread is blocked on a call to Send or Receive then calling Close will interrupt the socket on whatever blocking call it was in effectively unblocking it. I am sure there are several other areas in the BCL where similiar techniques can be used to unblock a thread.

通过Thread.Interrupt

这里的优点是它很简单,您不必真正专注于在代码中撒上任何东西.缺点是您几乎无法控制算法中安全点的位置.原因是因为 Thread.Interrupt 通过在其中一个罐装 BCL 阻塞调用中注入异常来工作.这些包括 Thread.SleepWaitHandle.WaitOneThread.Join 等.所以你必须明智地放置它们.然而,大多数时候算法决定了它们去哪里,这通常很好,特别是如果你的算法大部分时间都花在这些阻塞调用之一上.如果您的算法不使用 BCL 中的阻塞调用之一,则此方法对您不起作用.这里的理论是 ThreadInterruptException 仅从 .NET 等待调用生成,因此它可能处于安全点.至少您知道该线程不能处于非托管代码中,也不能退出临界区而使悬空锁处于已获取状态.尽管这比 Thread.Abort 侵入性小,但我仍然不鼓励使用它,因为哪个调用响应它并不明显,而且许多开发人员会不熟悉它的细微差别.

The advantage here is that it is simple and you do not have to focus on sprinkling your code with anything really. The disadvantage is that you have little control over where the safe points are in your algorithm. The reason is because Thread.Interrupt works by injecting an exception inside one of the canned BCL blocking calls. These include Thread.Sleep, WaitHandle.WaitOne, Thread.Join, etc. So you have to be wise about where you place them. However, most the time the algorithm dictates where they go and that is usually fine anyway especially if your algorithm spends most of its time in one of these blocking calls. If you algorithm does not use one of the blocking calls in the BCL then this method will not work for you. The theory here is that the ThreadInterruptException is only generated from .NET waiting call so it is likely at a safe point. At the very least you know that the thread cannot be in unmanaged code or bail out of a critical section leaving a dangling lock in an acquired state. Despite this being less invasive than Thread.Abort I still discourage its use because it is not obvious which calls respond to it and many developers will be unfamiliar with its nuances.

这篇关于关于在 .NET 中干净地终止线程的问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆