GON*_*ale 59 .net c# multithreading winforms
我理解Thread.Abort()在我读过的关于这个主题的大量文章中是邪恶的,所以我目前正在扯掉我的中止,以便以更清洁的方式取代它; 并且在比较了stackoverflow上的人们的用户策略之后,然后在阅读MSDN上的" 如何:创建和终止线程(C#编程指南) "之后,两者都说明了一种非常相似的方法 - 即使用volatile bool
方法检查策略,这很好,但我还有几个问题....
如果你没有一个简单的工作进程只是运行一个运算循环的代码,那么我的优势就在于此.比如说对我来说,我的进程是一个后台文件上传程序进程,我实际上是循环遍历每个文件,所以这是一些东西,并确保我可以while (!_shouldStop)
在顶部添加我的每个循环迭代,但我有更多的业务流程它发生在它下一次循环迭代之前发生,我希望这个取消程序是快节奏的; 不要告诉我,我需要在整个工作人员功能中每隔4-5行循环播放这些内容吗?!
我真的希望有一个更好的方法,有人可以告诉我这是否实际上是正确的[并且只有?]做到这一点的方法,或者他们过去用来实现我所追求的目标的策略.
谢谢帮派.
进一步阅读:所有这些SO响应都假设工作线程将循环.这并不适合我.如果它是线性的,但是及时的背景操作怎么办?
Bri*_*eon 102
不幸的是,可能没有更好的选择.这实际上取决于您的具体情况.我们的想法是在安全点优雅地停止线程.这就是为什么Thread.Abort
不好的原因; 因为不能保证在安全点发生.通过使用停止机制喷洒代码,您可以有效地手动定义安全点.这称为合作取消.这基本上有4种广泛的机制.您可以选择最适合您情况的那个.
轮询停止标志
您已经提到过这种方法.这是一个很常见的.定期检查算法中安全点的标志,并在发出信号时进行挽救.标准方法是标记变量volatile
.如果这不可能或不方便那么你可以使用lock
.请记住,volatile
如果lambda表达式通过闭包捕获它,则无法标记局部变量,那么您将不得不求助于另一种方法来创建所需的内存屏障.对于这种方法,没有其他需要说的东西.
使用TPL中的新取消机制
这与轮询停止标志类似,不同之处在于它使用TPL中的新取消数据结构.它仍然基于合作取消模式.你需要得到一个CancellationToken
并定期检查IsCancellationRequested
.要请求取消,您可以调用最初提供令牌Cancel
的CancellationTokenSource
那个.新的取消机制可以做很多事情.你可以在这里阅读更多信息.
使用等待句柄
如果工作线程需要在特定时间间隔内等待或在正常操作期间发出信号,则此方法非常有用.你可以Set
一个ManualResetEvent
,例如,让线程知道是时候停下来.您可以使用WaitOne
返回bool
指示事件是否已发出信号的函数来测试事件.将WaitOne
采用指定多少时间等待调用返回如果事件没有在该时间量信号的参数.您可以使用此技术代替Thread.Sleep
并同时获取停止指示.如果WaitHandle
线程可能必须等待其他实例,这也很有用.您可以WaitHandle.WaitAny
在一次通话中呼叫等待任何事件(包括停止事件).使用事件可能比调用更好Thread.Interrupt
因为您可以更好地控制程序流(Thread.Interrupt
抛出异常,因此您必须策略性地放置try-catch
块以执行任何必要的清理).
专业方案
有几种一次性场景具有非常专业的停止机制.绝对超出了这个答案的范围,列举所有这些(不要紧,这几乎是不可能的).我在这里的一个很好的例子是Socket
班级.如果线程在调用时被阻塞,Send
或者Receive
然后调用Close
将在任何阻塞调用中中断套接字,从而有效地解除阻塞.我相信在BCL中还有其他几个领域可以使用类似的技术来解锁一个线程.
通过中断线程 Thread.Interrupt
这里的优点是它很简单,你不必专注于用真正的代码洒你的代码.缺点是您几乎无法控制算法中安全点的位置.原因是因为Thread.Interrupt
在一个罐装BCL阻塞调用中注入异常.这些措施包括Thread.Sleep
,WaitHandle.WaitOne
,Thread.Join
等,所以你必须要聪明在哪里放置它们.但是,大多数情况下,算法决定了它们的去向,而且通常情况下通常都很好,特别是如果你的算法大部分时间花在这些阻塞调用之一上.如果算法没有使用BCL中的一个阻塞调用,那么此方法将不适合您.这里的理论是,ThreadInterruptException
它只是从.NET等待调用生成的,所以很可能在安全点.至少你知道线程不能处于非托管代码中,也不能脱离关键部分,从而在获得状态下留下悬空锁.虽然这种侵入性比Thread.Abort
我仍然不鼓励它的使用更少,因为不明显哪些呼叫响应它,许多开发人员将不熟悉它的细微差别.
Kir*_*ril 13
好吧,不幸的是,在多线程中,你经常不得不为了清洁而妥协"快速"...你可以立即退出一个线程Interrupt
,但它不会很干净.所以不,你不必_shouldStop
每隔4-5行清理一次支票,但是如果你确实打断了你的线程,那么你应该处理异常并以干净的方式退出循环.
即使它不是一个循环线程(也许它是一个线程执行一些长时间运行的异步操作或某种类型的块用于输入操作),你可以Interrupt
,但你仍然应该抓住ThreadInterruptedException
并彻底退出线程.我认为你读过的例子非常合适.
是的我有一个例子......我只会根据您引用的链接向您展示一个示例:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
如果取消是您正在构建的东西的要求,那么它应该被视为与您的其他代码一样多的尊重 - 它可能是您必须设计的东西.
让我们假设您的线程始终在做两件事之一.
如果您在相关线程中受到CPU约束,那么您可能有一个很好的位置来插入纾困检查.如果您正在调用其他人的代码来执行一些长时间运行的CPU绑定任务,那么您可能需要修复外部代码,将其移出进程(中止线程是邪恶的,但是中止进程是明确定义且安全的)等
如果你正在等待内核,那么等待中可能有一个句柄(或fd或mach端口......).通常,如果您销毁相关句柄,内核将立即返回一些失败代码.如果你在.net/java/etc.你最终可能会遇到异常.在C中,无论您已经处理系统调用失败的代码,都会将错误传播到应用程序的有意义部分.无论哪种方式,您都可以非常及时地打破低级别的地方,而无需在任何地方洒上新的代码.
我经常使用这种代码的一种策略是跟踪需要关闭的句柄列表,然后让我的中止函数设置一个"已取消"标志,然后关闭它们.当函数失败时,它可以检查标志并报告由于取消而导致的失败,而不是由于任何特定的异常/错误.
您似乎暗示取消的可接受粒度是在服务调用级别.这可能不是一个好主意 - 你最好同步取消后台工作并从前台线程加入旧的后台线程.它更清洁,因为:
当旧的bgwork线程在意外延迟后恢复生机时,它避免了一类竞争条件.
它通过使挂起的后台线程的效果可以隐藏,避免了由挂起后台进程引起的潜在隐藏线程/内存泄漏.
害怕这种方法有两个原因:
您认为不能及时中止自己的代码.如果取消是您的应用程序的要求,您真正需要做出的决定是资源/业务决策:做一个黑客,或干净地解决您的问题.
你不相信你正在调用的一些代码,因为它不受你的控制.如果您真的不信任它,请考虑将其移出进程外.您可以更好地隔离多种风险,包括这种风险.
归档时间: |
|
查看次数: |
35442 次 |
最近记录: |