中断一个正在睡觉的线程
本文关键字:线程 一个 中断 | 更新日期: 2023-09-27 18:08:54
是否有办法中断正在睡觉的线程?如果我有类似这样的代码。
while(true){
if(DateTime.Now.Subtract(_lastExecuteTime).TotalHours > 1){
DoWork();
_lastExecuteTime = DateTime.Now();
continue;
}
Thread.Sleep(10000) //Sleep 10 seconds
if(somethingIndicatingQuit){
break;
}
}
我想每小时执行DoWork()。所以,我想睡得比10秒长一点。每10分钟左右检查一次。但是,如果将我的睡眠设置为10分钟,并且我想要杀死此后台任务,则必须等待睡眠恢复。
我的实际代码是使用线程。ManualResetEvent关闭后台工作,但我的问题是ThreadSleep代码。如果有必要,我可以发布更多的代码。
好的,我将在这里添加一些更完整的代码,因为我认为它将回答一些问题。
private readonly ManualResetEvent _shutdownEvent = new ManualResetEvent(false);
private readonly ManualResetEvent _pauseEvent = new ManualResetEvent(true);
private Thread _backGroundWorkerThread;
//This starts our work
public void Start() {
_backGroundWorkerThread = new Thread(ExecuteWorker) {IsBackground = true, Name = WorkerName + "_Thread"};
_shutdownEvent.Reset();
_backGroundWorkerThread.Start();
}
internal void Stop() {
//Signal the shutdown event
_shutdownEvent.Set();
//Make sure to resume any paused threads
_pauseEvent.Set();
//Wait for the thread to exit
_backGroundWorkerThread.Join();
}
private void ExecuteWorker() {
while (true) {
_pauseEvent.WaitOne(Timeout.Infinite);
//This kills our process
if (_shutdownEvent.WaitOne(0)) {
break;
}
if (!_worker.IsReadyToExecute) {
//sleep 5 seconds before checking again. If we go any longer we keep our service from shutting down when it needs to.
Thread.Sleep(5000);
continue;
}
DoWork();
}
}
我的问题在这里,
_backGroundWorkerThread.Join();
等待线程。在后台线程中运行的ExecuteWorker()中睡觉。
不使用Thread.Sleep
,您可以使用超时的Monitor.Wait
-然后您可以使用Monitor.Pulse
从不同的线程唤醒它。
不要忘记在调用Wait
或Pulse
之前需要锁定监视器:
// In the background thread
lock (monitor)
{
// If we've already been told to quit, we don't want to sleep!
if (somethingIndicatingQuit)
{
break;
}
Monitor.Wait(monitor, TimeSpan.FromSeconds(10));
if (somethingIndicatingQuit)
{
break;
}
}
// To wake it up...
lock (monitor)
{
somethingIndicatingQuit = true;
Monitor.Pulse(monitor);
}
用ManualResetEvent.WaitOne
代替Thread.Sleep
。
while (true) {
if(DateTime.Now.Subtract(_lastExecuteTime).TotalHours > 1) {
DoWork();
_lastExecuteTime = DateTime.Now();
continue;
}
if (terminate.WaitOne(10000)) {
break;
}
}
其中terminate
是ManualResetEvent
1,可以通过Set
请求终止循环。
更新:
我刚刚注意到你说你已经使用ManualResetEvent
来终止后台工作(我假设这是在DoWork
中)。有什么原因你不能使用相同的MRE吗?如果这是不可能的,当然不应该使用一个不同的问题。
是的,所以ExecuteWorker
中的Thread.Sleep(5000)
换成_shutdownEvent.WaitOne(5000)
。它看起来像下面这样:
private void ExecuteWorker() {
while (true) {
_pauseEvent.WaitOne(Timeout.Infinite);
//This kills our process
if (_shutdownEvent.WaitOne(0)) {
break;
}
if (!_worker.IsReadyToExecute) {
//sleep 5 seconds before checking again. If we go any longer we keep our service from shutting down when it needs to.
_shutdownEvent.WaitOne(5000);
continue;
}
DoWork();
}
}
1在。net 4.0中也有一个ManualResetEventSlim
类
可以使用head。中断以唤醒正在睡觉的线程。它将导致阻塞线程上的ThreadInteruptedException
,因此它不是最优雅或最有效的方法。
您还需要意识到以这种方式中断线程是不安全的。它不提供对线程终止点的控制,因此不应该在没有仔细考虑后果的情况下使用。正如在其他答案中已经提到的,使用基于信号的技术来控制何时以受控的方式终止线程要好得多。
为什么不使用BlockingCollection呢?我认为这是一个比公认的答案更优雅的方法。我也不认为与显示器相比有太多的开销。
方法如下:
初始化一个BlockingCollection作为成员:
BlockingCollection<int> _sleeper = new BlockingCollection<int>();
代替
Thread.Sleep(10000)
这样做
int dummy;
_sleeper.TryTake(out dummy, 10000);
唤醒它,你可以使用下面的代码
_sleeper.Add(0);
这里有三种方法来实现一个可取消的Sleep
方法,使用Task.Delay
+ CancellationToken
组合。第一个必须处理AggregateException
以防取消,所以它有点冗长:
public static void Sleep(int millisecondsTimeout, CancellationToken cancellationToken)
{
try
{
Task.Delay(millisecondsTimeout, cancellationToken).Wait();
}
catch (AggregateException aex) when (aex.InnerException is OperationCanceledException)
{
// Ignore exception
}
}
第二个处理一个OperationCanceledException
,所以它更好:
public static void Sleep(int millisecondsTimeout, CancellationToken cancellationToken)
{
try
{
Task.Delay(millisecondsTimeout).Wait(cancellationToken);
}
catch (OperationCanceledException)
{
// Ignore exception
}
}
第三个语句不需要捕获异常,因此它是最简洁的:
public static void Sleep(int millisecondsTimeout, CancellationToken cancellationToken)
{
Task.WaitAny(Task.Delay(millisecondsTimeout, cancellationToken));
}
Task.WaitAny
接受params Task[]
作为参数,因此它可能比前面的选项效率稍低。
关于取消的更多信息:托管线程中的取消
你不能直接包装你的线程吗?循环中的睡眠部分,这样循环60次,然后再进行另一次检查?当然,所有这些都是在简单的if(somethingIndicatingQuit)与DateTIme if检查之间进行交换,但是假设您的实际代码可能要做一些更昂贵的事情,那么使用第二个循环可能会节省一些CPU。
如果你想每小时DoWork(),为什么不计算所需的毫秒数,直到下一个小时,并等待那个时间?为了取消睡眠,你可以使用一个可等待的同步类,就像@Jon Skeet建议的监视器,或者只是在线程中设置一个标志,告诉它在睡眠后退出,而不是DoWork(),然后忘记它-要么线程会在时间到的时候杀死自己,要么你的操作系统会在应用程序关闭时杀死它,以先到者为例。
祝好,马丁
可以将Sleep(...)
方法包装为SafeSleep(...)
方法,可以用Thread.Interrupt();
中断:
private void SafeSleep(int time)
{
try
{
Thread.Sleep(time);
}catch(ThreadInterruptedException)
{
Log.Debug("Thread sleep interrupted");
}
}
现在线程将在睡眠时间或Thread.Interrupt()
调用之前睡眠。由于异常将被捕获,控制将返回到线程正常流,您可以立即终止它。