【问题标题】:Interrupt a sleeping Thread中断一个休眠的线程
【发布时间】:2011-09-16 17:57:47
【问题描述】:

有没有办法中断睡眠线程?如果我有类似的代码。

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 分钟,并且我想终止这个后台任务,我必须等待睡眠恢复。

我的实际代码是使用 Threading.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。

【问题讨论】:

  • 您不使用计时器有什么具体原因吗?您可以将该代码减去循环放在一个方法中,并将其作为每 n 分钟执行一次的 Timer 的回调传递,然后停止任务只需终止 Timer 或将其间隔设置为 Infinite。
  • 我已经发布了更多代码。此代码的类是管理无限数量的父类的子类。父控制这些子类的启动、停止、暂停和继续以及它们正在做的工作。需要支持的不是简单的启动和停止问题。
  • 这里的_worker 是什么?
  • 只是一个具有调度逻辑的类的实例。它的方法 IsReadyToExecute 返回一个布尔值。 _worker 与这个问题无关。

标签: c# multithreading


【解决方案1】:

您可以使用带有超时的Monitor.Wait,而不是使用Thread.Sleep - 然后您可以使用来自不同线程的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);
}

【讨论】:

  • 我会试试这个。如果它适用于我的情况,我会确保我投票并接受你的回答。谢谢乔恩。
  • 如果 Jon Skeet 给了你一个答案,但它不起作用,那么你的答案是错误的 :)
  • 另外,Jon,你已经用 lock(monitor) 替换了我的无限循环 --> while(true)。不知道那对我有什么作用。另外, Monitor.Pulse();给我一个例外,方法 'Pulse' 有 1 个参数,但使用 0 个参数调用。
  • 这是一个很好的答案 - 解决了我的问题,这是我找到的最好的 Monitor 示例。一个小小的请求 - 您能否更新您的答案以澄清 monitor 变量是什么?对某些人来说可能很明显,但我不得不环顾四周。谢谢!
  • 确实很好的答案 - 下面的这个链接也很有帮助:albahari.com/threading/…
【解决方案2】:

不要使用Thread.Sleep,而是使用ManualResetEvent.WaitOne

while (true) {
    if(DateTime.Now.Subtract(_lastExecuteTime).TotalHours > 1) {
        DoWork();
        _lastExecuteTime = DateTime.Now();
        continue; 
    }
    if (terminate.WaitOne(10000)) {
        break;
    }
}

其中terminateManualResetEvent1,您可以通过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 类。

【讨论】:

  • 谢谢布赖恩。这正是我所需要的。我在所有测试中运行了这个解决方案,效果很好!现在打电话停止,停止我的工人!很高兴接受圣路易斯同胞的回答!
  • @Jeff:很好,你是我第一个在这里遇到的 :)
【解决方案3】:

您可以使用Thead.Interrupt 唤醒休眠线程。它会在被阻塞的线程上导致ThreadInteruptedException,所以这不是最优雅或最有效的方法。

您还需要担心以这种方式中断线程是不安全的。它不提供对线程中止点的控制,因此不应在未仔细考虑后果的情况下使用。正如其他答案中已经提到的,使用基于信号的技术来控制线程何时以受控方式终止要好得多。

【讨论】:

  • 以何种方式不安全?它在阻塞线程上抛出一个ThreadInteruptedException。也许你可以详细说明?我猜你的意思是它是否因为睡眠以外的原因而阻塞?
  • 不安全,因为你无法知道被中断的线程处于什么状态。线程不会在“安全点”被中断——它会在任何地方被中断。这可能是应用程序状态未定义或不正确或完全错误的时间点。
  • 公平点,我将添加一个注释以供参考。我知道有更好的选择,只是不想重复相同的答案。
  • Thread.Interrupt 安全的。 Thread.Abort 不安全。 Interrupt 只会导致 BCL 等待调用(如 JoinSleep 等)立即返回。除非您的代码有问题,否则ThreadInterruptException 应该 在安全点注入并且您确实 可以完全控制。如果您决定使用Interrupt,请注意阻塞调用的位置。 再一次,中断和中止线程是两件非常不同的事情!
  • 它只在下一个块上中断,但我想重点是它不一定因为睡眠而阻塞。
【解决方案4】:

为什么不使用 BlockingCollection?我认为这是一种比公认的答案更优雅的方法。我也不认为与显示器相比有太多开销。

这是怎么做的:

将 BlockingCollection 初始化为成员:

BlockingCollection<int> _sleeper = new BlockingCollection<int>();

插入

Thread.Sleep(10000)

这样做

int dummy;
_sleeper.TryTake(out dummy, 10000);

要唤醒它,您可以使用此代码

_sleeper.Add(0);

【讨论】:

    【解决方案5】:

    以下是实现可取消的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
        }
    }
    

    第三个没有异常可以catch,所以最简洁:

    public static void Sleep(int millisecondsTimeout, CancellationToken cancellationToken)
    {
        Task.WaitAny(Task.Delay(millisecondsTimeout, cancellationToken));
    }
    

    Task.WaitAny 接受 params Task[] 作为参数,因此它的效率可能略低于之前的选项。

    关于一般取消的更多信息:Cancellation in Managed Threads

    【讨论】:

      【解决方案6】:

      您不能将 Thread.Sleep 部分包装在一个循环中,使其循环 60 次,然后再进行另一次检查吗?当然,所有这一切都是在简单的 if(somethingIndicatingQuit) 与 DateTIme if 检查之间进行权衡,但假设你的真实代码可能会做一些更昂贵的事情,第二个循环可能会节省一些 CPU .

      【讨论】:

        【解决方案7】:

        如果您想每小时执行一次 DoWork(),为什么不计算下一个小时所需的毫秒数并等待那个时间呢?要取消睡眠,您可以使用可等待的同步类,例如@Jon Skeet 建议的监视器,或者只是在线程中设置一个标志,告诉它在睡眠后退出而不是 DoWork() 并忘记它- 线程将在时间到时自行终止,或者您的操作系统将在应用程序关闭时终止它,以先到者为准。

        Rgds, 马丁

        【讨论】:

        • 不是应用关闭。它在服务中运行,并且必须支持停止、暂停和继续。目前,如果我将线程设置为长时间睡眠,则暂停或停止需要太长时间,因为它必须等待线程从睡眠状态恢复。
        【解决方案8】:

        您可以将Sleep(...) 方法包装到SafeSleep(...) 方法可以用Thread.Interrupt(); 中断:

        private void SafeSleep(int time)
        {
            try
            {
                Thread.Sleep(time);
            }catch(ThreadInterruptedException)
            {
                Log.Debug("Thread sleep interrupted");
            }
        }
        

        现在线程将在睡眠时间或调用Thread.Interrupt() 之前睡眠。由于异常会被捕获,控制权会返回到Thread正常流程中,您可以直接终止它。

        【讨论】:

          猜你喜欢
          • 2023-03-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-06-01
          • 1970-01-01
          • 2019-08-12
          相关资源
          最近更新 更多