为什么Thread.Sleep如此有害


128

我经常看到它提到Thread.Sleep();不应使用,但我不明白为什么会这样。如果Thread.Sleep();会引起麻烦,是否有其他相同结果的替代解决方案是安全的?

例如。

while(true)
{
    doSomework();
    i++;
    Thread.Sleep(5000);
}

另一个是:

while (true)
{
    string[] images = Directory.GetFiles(@"C:\Dir", "*.png");

    foreach (string image in images)
    {
        this.Invoke(() => this.Enabled = true);
        pictureBox1.Image = new Bitmap(image);
        Thread.Sleep(1000);
    }
}

3
该博客的摘要可能是“不要滥用Thread.sleep()”。
马丁·詹姆斯

5
我不会说这是有害的。我想说的是,例如goto:,可能存在比更好的解决方案Sleep
默认值

9
它与并不完全相同goto,后者更像是代码气味而不是设计气味。编译器goto在代码中插入s并没有错:计算机不会感到困惑。但是Thread.Sleep并不完全相同;编译器不会插入该调用,它还会带来其他负面影响。但是,是的,人们普遍认为使用它是错误的,因为几乎总有更好的解决方案肯定是正确的。
科迪·格雷

31
每个人都对上述示例为什么不好提出了意见,但是没有人提供不使用Thread.Sleep()的重写版本,该版本仍然可以实现给定示例的目标。
StingyJack

3
每次您sleep()输入代码(或测试)时,一只小狗都会死亡
Reza S

Answers:


163

与调用问题Thread.SleepARE 相当简洁解释在这里

Thread.Sleep它的用途:在MTA线程上测试/调试时模拟冗长的操作。在.NET中,没有其他理由使用它。

Thread.Sleep(n)表示将当前线程阻塞至少n 几毫秒内可能发生的时间片(或线程数量)。时间片的长度在Windows的不同版本/类型和不同的处理器上是不同的,并且通常在15到30毫秒之间。这意味着几乎可以保证线程阻塞的时间超过了n毫秒。n毫秒之后,线程将完全重新唤醒的可能性几乎是不可能的。 因此,Thread.Sleep计时是没有意义的

线程是有限的资源,它们大约需要200,000个周期来创建,而销毁大约100,000个周期。默认情况下,它们为其堆栈保留1 MB的虚拟内存,并为每个上下文切换使用2,000-8,000个周期。这使得任何等待线程都是 巨大的浪费。

首选解决方案:WaitHandles

最错误的做法是使用Thread.Sleepwhile构造(演示和答案不错的博客条目

编辑:
我想增强我的答案:

我们有2个不同的用例:

  1. 我们正在等待,因为我们知道一个特定的时间跨度时,我们应该继续(使用Thread.SleepSystem.Threading.Timer或相似者)

  2. 我们正在等待,因为某些条件会改变一段时间...关键字会持续一段时间!如果条件检查在我们的代码域中,则应使用WaitHandles-否则,外部组件应提供某种类型的钩子……如果不是,则其设计不好!

我的回答主要涵盖用例2


29
考虑到当今的硬件,我不会将1 MB的内存称为巨大浪费
默认情况下

14
@Default嘿,与原始作者讨论这个问题:),并且,它始终取决于您的代码-或更好:因素...并且主要问题是“线程是有限的资源”-今天的孩子们并不了解关于某些实现的效率和成本,因为“硬件便宜”……但有时您需要编写非常喜欢的代码
Andreas Niedermair

11
“这使任何等待的线程都成为巨大的浪费”是吗?如果某些协议规范要求先暂停一秒钟再继续,那么要等待一秒钟呢?某个地方的某个线程将不得不等待!线程创建/销毁的开销通常是无关紧要的,因为无论如何,出于其他原因,都必须提升线程,并且该线程将在进程的整个生命周期内运行。我希望看到一种避免上下文切换的方法,当规格说明为“打开泵后,至少要等待十秒钟使压力稳定,然后再打开进料阀”。
马丁·詹姆斯

9
@CodyGray-我再次阅读了该帖子。我的评论中没有看到任何颜色的鱼。Andreas退出网络:“ Thread.Sleep的用途是:在MTA线程上进行测试/调试时,模拟冗长的操作。在.NET中,没有其他理由使用它。我认为很多应用程序都需要sleep()调用。如果开发人员的leigons(对于很多人)坚持使用sleep()循环作为条件监视器,应将其替换为events / condvars / semas /等等,那么就断言“没有其他理由使用它”是没有道理的'。
马丁·詹姆斯

8
在30多年的多线程应用程序开发(主要是C ++ / Delphi / Windows)中,我从未见过任何可交付代码中对sleep(0)或sleep(1)循环的任何需求。有时,我出于调试目的而使用了此类代码,但从未将其提供给客户。“如果您编写的东西不能完全控制每个线程” –线程的微观管理和开发人员的微观管理一样是一个大错误。线程管理是OS的功能-应该使用它提供的工具。
马丁·詹姆斯

34

场景1-等待异步任务完成:我同意在线程正在等待另一个线程上的任务完成的情况下使用WaitHandle / Auto | ManualResetEvent。

场景2-计时循环:但是,作为一种粗略的计时机制(while + Thread.Sleep)非常适合99%的应用程序,而这些应用程序不需要确切了解被阻塞的线程何时应该“唤醒*”。创建线程的200k个周期也是无效的-无论如何都需要创建定时循环线程,而200k个周期只是另一个大数字(告诉我打开一个文件/套接字/ db调用需要多少个周期?)。

因此,如果while + Thread.Sleep起作用,为什么使事情复杂化?只有语法律师才是实用的


值得庆幸的是,我们现在有了TaskCompletionSource,可以有效地等待结果。
奥斯丁·萨尔加特

13

我想从编码政治的角度回答这个问题,这可能对所有人都没有帮助。但是特别是当您使用面向9-5位公司程序员的工具时,编写文档的人往往会使用诸如“应该”和“从不”之类的词来表示“除非您真正了解自己,否则请不要这样做”在做什么以及为什么”。

我在C#领域中的其他几个我最喜欢的地方是,它们告诉您“从不调用lock(this)”或“从不调用GC.Collect()”。在许多博客和官方文档中都强行声明了这两个信息,而IMO则是完全错误的信息。在某种程度上,这种错误信息可以达到其目的,因为它使初学者无法在完全研究替代方案之前就去做他们不了解的事情,但与此同时,这使得通过搜索引擎很难找到真正的信息。似乎指向一些文章,告诉您不要对“为什么不这样做”这个问题没有答案。

从政治上讲,它归结为人们认为“好的设计”或“不好的设计”。官方文档不应该决定我的应用程序的设计。如果确实有技术上的原因,您不应该调用sleep(),则IMO文档应指出完全可以在特定情况下调用它,但也许可以提供一些独立于场景的替代解决方案,或者更适合其他情况场景。

在许多情况下,在以实时方式明确定义了期限的情况下,明确地调用“ sleep()”很有用,但是,在开始抛出sleep()之前,有一些更复杂的系统用于等待和发信号通知线程,应当予以考虑和理解。 )添加到您的代码中,然后在代码中抛出不必要的sleep()语句通常被视为初学者的策略。


5

人们注意的是示例的1).spinning和2).polling循环,而不是Thread.Sleep()部分。我认为通常添加Thread.Sleep()可以轻松地改善正在旋转或轮询循环的代码,因此它仅与“错误”代码相关联。

此外,人们还会做以下事情:

while(inWait)Thread.Sleep(5000); 

不能以线程安全的方式访问变量inWait,这也会引起问题。

程序员希望看到的是由事件,信号和锁定结构控制的线程,并且当您这样做时,就不需要Thread.Sleep()了,并且消除了对线程安全变量访问的担忧。例如,您可以创建与FileSystemWatcher类关联的事件处理程序,并使用事件来触发第二个示例而不是循环吗?

正如Andreas N.提到的那样,请阅读Joe Albahari的C#中的Threading,它确实非常好。


那么,执行代码示例中的替代方法是什么?
shinzou

kuhaku-是的,很好的问题。显然,Thread.Sleep()是一个快捷方式,有时是一个大的快捷方式。对于上面的示例,轮询循环“ while(inWait)Thread.Sleep(5000);”之下函数中的其余代码是一个新函数。该新函数是一个委托(回调函数),您可以将其传递给设置“ inWait”标志的任何内容,而不是更改“ inWait”标志,而是调用回调。这是我能找到的最短的示例:myelin.co.nz/notes/callbacks/cs-delegates.html
mike

只是为了确保我明白了,您的意思是要包装Thread.Sleep()另一个函数,并在while循环中调用它?
shinzou

5

如果无法控制的独立程序有时可能会使用常用资源(例如文件),并且程序在运行时以及这些资源正在使用该资源时需要使用睡眠,则使用睡眠您的程序被禁止使用的其他程序。在这种情况下,您在代码中访问资源时,将对资源的访问置于try-catch中(以在无法访问资源时捕获异常),并将其置于while循环中。如果资源是空闲的,则永远不会调用睡眠。但是,如果资源被阻止,则您需要睡眠一段适当的时间,然后尝试再次访问该资源(这就是循环的原因)。但是,请记住,必须在循环上放置某种限制器,因此它不是潜在的无限循环。


3

我有一个用例,我在这里看不到,并认为这是使用Thread.Sleep()的正当理由:

在运行清理作业的控制台应用程序中,我需要对数千个并发用户共享的数据库进行大量相当昂贵的数据库调用。为了不影响数据库并在数小时内排除其他数据库,我需要在两次调用之间停顿一下,大约100毫秒。这与计时无关,只是与其他线程对数据库的访问有关。

花费2000-8000个周期在调用之间进行上下文切换可能花费500毫秒,这是无害的,线程拥有1 MB的堆栈也是如此,线程在服务器上作为单个实例运行。


是的,Thread.Sleep在您描述的单线程,单用途控制台应用程序中使用完全可以。
Theodor Zoulias

-7

我同意这里的许多观点,但我也认为这要看情况。

最近我做了这段代码:

private void animate(FlowLayoutPanel element, int start, int end)
{
    bool asc = end > start;
    element.Show();
    while (start != end) {
        start += asc ? 1 : -1;
        element.Height = start;
        Thread.Sleep(1);
    }
    if (!asc)
    {
        element.Hide();
    }
    element.Focus();
}

这是一个简单的动画功能,我使用Thread.Sleep了它。

我的结论是,如果能做到,那就使用它。


-8

对于那些在SCENARIO 2中没有看到反对使用Thread.Sleep的有效论点的人来说,确实有一个-应用程序退出由while循环阻止(SCENARIO 1/3只是愚蠢的,因此不值得更多提及)

许多假装对Thread.Sleep恶名昭彰的人未能为我们中那些要求不使用它的实际原因提一个单一的正当理由-但在这里要感谢Pete- Thread.Sleep是邪恶的(可以使用计时器/处理程序轻松避免)

    static void Main(string[] args)
    {
        Thread t = new Thread(new ThreadStart(ThreadFunc));
        t.Start();

        Console.WriteLine("Hit any key to exit.");
        Console.ReadLine();

        Console.WriteLine("App exiting");
        return;
    }

    static void ThreadFunc()
    {
        int i=0;
        try
        {
            while (true)
            {
                Console.WriteLine(Thread.CurrentThread.ThreadState.ToString() + " " + i);

                Thread.Sleep(1000 * 10);
                i++;
            }
        }
        finally
        {
            Console.WriteLine("Exiting while loop");
        }
        return;
    }

9
-,Thread.Sleep不是,这不是这样做的原因(连续的while循环是新线程)!您可能只是删除了Thread.Sleep-line-等:程序也不会退出...
Andreas Niedermair
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.