中断一个正在睡觉的线程

本文关键字:线程 一个 中断 | 更新日期: 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从不同的线程唤醒它。

不要忘记在调用WaitPulse之前需要锁定监视器:

// 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;
    }
}

其中terminateManualResetEvent 1,可以通过Set请求终止循环。

更新:

我刚刚注意到你说你已经使用ManualResetEvent来终止后台工作(我假设这是在DoWork中)。有什么原因你不能使用相同的MRE吗?如果这是不可能的,当然不应该使用一个不同的问题。

更新2:

是的,所以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()调用之前睡眠。由于异常将被捕获,控制将返回到线程正常流,您可以立即终止它。