为什么线程.睡眠如此有害

本文关键字:线程 为什么 | 更新日期: 2023-09-27 18:20:09

我经常看到有人提到不应该使用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);
    }
}

为什么线程.睡眠如此有害

这里非常简洁地解释了调用Thread.Sleep的问题:

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

Thread.Sleep(n)表示阻塞当前线程至少一个数字n内可能出现的时间片(或线程数量)毫秒。不同版本/类型的时间片长度不同Windows和不同的处理器,通常在15到30之间毫秒。这意味着线程几乎可以保证阻塞大于CCD_ 7毫秒。您的线程将完全在n毫秒后重新唤醒与不可能。所以,Thread.Sleep对计时来说毫无意义。

线程是一种有限的资源,大约需要200000个周期创造和摧毁约100000个循环。默认情况下,它们为其堆栈保留1兆字节的虚拟内存,并使用2000-8000每个上下文切换的周期这使得任何等待线程巨大的浪费

首选解决方案:WaitHandles

最常见的错误是使用带有while结构的Thread.Sleep(演示和回答,不错的博客条目)

编辑:
我想加强我的回答:

我们有两种不同的用例:

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

  2. 我们在等待,因为某些情况会随着时间的推移而改变。。。关键字是/are一段时间!如果条件检查在我们的代码域中,我们应该使用WaitHandles,否则外部组件应该提供某种挂钩。。。如果它不这样做,它的设计就很糟糕!

我的回答主要涉及用例2

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

场景2-循环计时:然而,作为粗略的定时机制(while+Thread.Sleep),对于99%的应用程序来说是非常好的,这些应用程序不需要确切地知道被阻止的线程何时应该"唤醒"。创建线程需要20万个周期的说法也是无效的-无论如何都需要创建定时循环线程,而20万个循环只是另一个大数字(告诉我打开一个文件/socket/db调用的周期是多少?)。

那么,如果while+Thread.Sleep有效,为什么要把事情复杂化呢?只有语法律师才是实用的!

我想从编码政治的角度回答这个问题,这可能对任何人都有帮助,也可能没有帮助。但尤其是当你使用的工具是为朝九晚五的公司程序员设计的时,编写文档的人倾向于使用"不应该"answers"永远"等词来表示"除非你真的知道自己在做什么和为什么,否则不要这样做"。

在C#世界里,我最喜欢的几个是,它们告诉你"永远不要调用lock(this)"或"永远不要使用GC.Collect()"。这两个词在许多博客和官方文档中都被强烈声明,IMO完全是错误信息。在某种程度上,这种错误信息起到了一定的作用,因为它让初学者在充分研究替代品之前远离他们不理解的事情,但同时,它也使人们很难通过搜索引擎找到真实的信息,这些信息似乎都指向告诉你不要做某事的文章,而却没有回答"为什么不做?"

在政治上,它可以归结为人们认为的"好设计"或"坏设计"。官方文件不应该决定我的申请的设计。如果确实有技术原因不应该调用sleep(),那么IMO文档应该说明在特定场景下完全可以调用它,但可能会提供一些独立于场景或更适合其他场景的替代解决方案。

显然,在许多情况下,当最后期限在现实世界中明确定义时,调用"sleep()"是有用的。然而,在开始向代码中抛出sleep(。

人们注意的是示例的1).sspinning和2).polling循环,而不是Thread.Sleep()部分。我认为添加Thread.Sleep()通常是为了方便地改进正在旋转或处于轮询循环中的代码,所以它只与"坏"代码相关。

此外,人们会做一些事情,比如:

while(inWait)Thread.Sleep(5000); 

其中变量inWait没有以线程安全的方式访问,这也会导致问题。

程序员希望看到的是由Events、Signaling和Locking结构控制的线程,当您这样做时,您将不需要Thread.Sleep(),并且还消除了对线程安全变量访问的担忧。举个例子,你能创建一个与FileSystemWatcher类相关的事件处理程序,并使用一个事件来触发你的第二个例子而不是循环吗?

正如Andreas N.提到的,阅读Joe Albahari的《C#线程》,它真的非常好。

Sleep用于您无法控制的独立程序有时可能使用常用资源(例如文件)的情况,您的程序在运行时需要访问该资源,并且当这些其他程序正在使用该资源时,您的程序被阻止使用该资源。在这种情况下,当您访问代码中的资源时,您将对资源的访问放在try-catch中(在无法访问资源时捕获异常),并将其放在while循环中。如果资源是空闲的,则永远不会调用睡眠。但是,如果资源被阻止,那么您将睡眠一段适当的时间,并尝试再次访问该资源(这就是您循环的原因)。然而,请记住,您必须在循环上设置某种限制器,这样它就不是一个潜在的无限循环。您可以将您的限制条件设置为N次尝试(这是我通常使用的),或者检查系统时钟,添加固定的时间量以获得时间限制,如果达到时间限制,则退出尝试访问。

我有一个用例,我认为这里没有涵盖,我会争辩说这是使用Thread.Sleep()的正当理由:

在运行清理作业的控制台应用程序中,我需要对数千个并发用户共享的数据库进行大量相当昂贵的数据库调用。为了不冲击数据库并将其他线程排除在外数小时,我需要在调用之间暂停100毫秒左右。这与时间无关,只是为了让其他线程访问数据库。

花费2000-8000个周期在可能需要500毫秒才能执行的调用之间切换上下文是良性的,线程有1MB的堆栈也是如此,它作为服务器上的单个实例运行。

我认为使用计时器是一个很好的选择。如果您希望某个操作每x毫秒执行一次(如果您使用Thread.sleep(x)),则您的操作将每x+y秒执行一次,其中y是执行操作所需的时间。

对于那些还没有看到反对使用Thread.Sleep的有效论点的人来说,在SCENARIO 2中,确实有一个-应用程序出口被while循环阻塞(1/3的场景非常愚蠢,所以不值得更多提及)

许多假装知情的人尖叫着Thread.睡眠是邪恶的,但对于我们这些要求实际理由不使用它的人来说,没有提到一个有效的理由-但多亏了Pete,它就在这里。睡眠是邪恶(可以通过计时器/处理程序轻松避免)

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

我同意这里的很多观点,但我也认为这取决于情况。

最近我做了这个代码:

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

我的结论是,如果它完成了任务,就使用它。