【问题标题】:Is it always bad to use Thread.Sleep()?使用 Thread.Sleep() 总是不好的吗?
【发布时间】:2015-08-07 08:32:06
【问题描述】:

我为 Random 类创建了一个扩展方法,它在随机时间执行 Action(无效委托):

public static class RandomExtension
{
    private static bool _isAlive;
    private static Task _executer;

    public static void ExecuteRandomAsync(this Random random, int min, int max, int minDuration, Action action)
    {
        Task outerTask = Task.Factory.StartNew(() =>
        {
            _isAlive = true;
            _executer = Task.Factory.StartNew(() => { ExecuteRandom(min, max, action); });
            Thread.Sleep(minDuration);
            StopExecuter();
        });
    }

    private static void StopExecuter()
    {
        _isAlive = false;
        _executer.Wait();

        _executer.Dispose();
        _executer = null;
    }

    private static void ExecuteRandom(int min, int max, Action action)
    {
        Random random = new Random();

        while (_isAlive)
        {
            Thread.Sleep(random.Next(min, max));
            action();
        }
    }
}

效果很好。

但是在这个例子中使用Thread.Sleep() 可以吗,或者你通常不应该使用Thread.Sleep(),会出现什么并发症?有替代品吗?

【问题讨论】:

    标签: c# multithreading sleep


    【解决方案1】:

    使用Thread.Sleep 不好?一般不会,如果你真的想挂起线程。但在这种情况下,您不想暂停线程,而是想暂停任务

    所以在这种情况下,你应该使用:

    await Task.Delay(minDuration);
    

    这不会挂起整个线程,而只会挂起您要挂起的单个任务。同一线程上的所有其他任务都可以继续运行。

    【讨论】:

    • 我不明白有什么区别,任务不只是一个托管线程吗? Thread.Sleep() 也只会阻塞当前任务,否则我不会工作。
    • 请记住,如果您的应用程序是基于 GUI 而不是控制台应用程序,那么操作系统(或窗口管理器)将期望线程执行其他操作,而不仅仅是处理您的代码。具体来说,它希望主线程处理发送给它的消息,例如鼠标事件、控制处理和其他内务处理。它通常在空闲时做这些事情,但睡眠不被认为是空闲的。如果您的线程没有响应这些消息,操作系统、窗口管理器或用户可能会认为它已锁定。
    • @NightSkyCode 你为什么不能等待呢?
    【解决方案2】:

    我会使用Task.Delay 而不是Thread.Sleep 的一个原因是您可以将CancellationToken 传递给它。如果用户想要StopExecutor 并且随机收到很长的持续时间跨度,您最终会阻塞很长时间。另一方面,在Task.Delay 中,您可以取消操作,它会收到取消通知。

    我认为您选择的设计还有其他问题。 Random 类并不真正适合 成为任务调度程序。我会觉得找到ExecuteRandomAsync 有点奇怪,因为它通常不会随机执行,而是每 X 分钟执行一些任意的Action

    我会改用另一种方式来解决这个问题。在保留您已经创建的大部分内部结构的同时,将它们放在不同的类中。

    public class ActionInvoker
    {
        private readonly Action _actionToInvoke;
    
        public ActionInvoker(Action actionToInvoke)
        {
            _actionToInvoke = actionToInvoke;
            _cancellationTokenSource = new CancellationTokenSource();
        }
    
        private readonly CancellationTokenSource _cancellationTokenSource;
        private Task _executer;
    
        public void Start(int min, int max, int minDuration)
        {
            if (_executer != null)
            {
                return;
            }
    
            _executer = Task.Factory.StartNew(
                        async () => await ExecuteRandomAsync(min, max, _actionToInvoke),
                        _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, 
                        TaskScheduler.Default)
                        .Unwrap();
        }
    
        private void Stop()
        {
            try
            {
                _cancellationTokenSource.Cancel();
            }
            catch (OperationCanceledException e)
            {
                // Log the cancellation.
            }
        }
    
        private async Task ExecuteRandomAsync(int min, int max, Action action)
        {
            Random random = new Random();
    
            while (!_cancellationTokenSource.IsCancellationRequested)
            {
                await Task.Delay(random.Next(min, max), _cancellationTokenSource.Token);
                action();
            }
        }
    }
    

    【讨论】:

    • 持续时间在哪里实施?
    • @Berntonline 你不需要睡两次。
    【解决方案3】:

    Sleep 与您要挂起线程的操作系统“对话”。这是资源密集型操作,因为您的线程无论如何都使用 RAM(尽管它不需要处理时间)。

    使用线程池,您可以使用线程的资源(例如 RAM)来处理其他一些小任务。为此,Windows 允许您将线程置于 睡眠 处于特殊警报状态,因此它可能会被唤醒并临时使用。

    所以Task.Delay 让您将线程置于警报睡眠状态,从而让您在不需要它们的情况下使用这些线程的资源。

    【讨论】:

      【解决方案4】:

      这样想。

      休眠线程是危险的,因为通常不止一个任务依赖于该线程,更不用说程序的关键组件也可能这样做。 永远不要在线程上使用sleep 是不合适的,当它们对您的程序有益时,您会想要使用它们。

      由于不可预测的行为,很多人都被教导不要休眠线程。这就像管理一个团队,最终你将不得不让一些人去吃午饭,只要其余的人都在工作,而你选择去吃午饭的人不在,那么你应该没问题,还能继续工作.

      如果项目中有人依赖他们的存在,请不要让他们吃午饭。

      【讨论】:

      • 嗯,这个例子有用吗,还是我应该使用其他东西?
      • @PatrickHofman 已经深入研究了整个线程睡眠的替代方案......尽管没有真正的线程睡眠替代方案,而是通过其他措施(线程外部或逻辑内部)解决问题。
      【解决方案5】:

      所有答案都是正确的,我想补充一个实用的观点:

      当您有一个远程非实时组件要测试时(例如,搜索引擎),您需要在再次引用它进行断言之前给它时间进入它的新状态。换句话说,您想暂停测试一段时间。由于测试本身的性能无关紧要(请区分测试的性能和组件的性能),您有时更愿意保留您的代码(测试的)尽可能简单直接,您甚至可以避免异步代码的最小复杂性。你当然可以开始一个新的测试,而不是等待你的组件(这是await Task.Delay()Thread.Sleep() 之间的区别),但假设你并不急于这样做。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-03-28
        • 1970-01-01
        • 2016-01-13
        • 1970-01-01
        • 2016-07-03
        • 2013-07-23
        • 1970-01-01
        相关资源
        最近更新 更多