为什么Thread.Sleep如此有害

时间:2012-01-11 07:52:55

标签: c# multithreading sleep

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

8 个答案:

答案 0 :(得分:144)

调用Thread.Sleep的问题是explained quite succinctly here

  

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

     

Thread.Sleep(n)表示至少阻止当前线程的数量   n内可能出现的时间片(或线程量子)   毫秒。   时间片的长度在不同版本/类型上是不同的   Windows和不同的处理器,通常在15到30之间   毫秒。这意味着线程几乎可以保证阻塞   超过n毫秒。你的线程可能性   在n毫秒之后完全重新唤醒是不可能的   不可能的。 因此,Thread.Sleep对于时间安排毫无意义

     

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

首选解决方案:WaitHandles

最常犯的错误是使用Thread.Sleep和while-construct(demo and answernice blog-entry

编辑:
我想提高我的答案:

  

我们有两种不同的用例:

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

  2.   
  3. 我们正在等待,因为有些情况会改变一段时间......   关键字某段时间!如果条件检查在我们的代码域中,我们   应该使用WaitHandles - 否则外部组件应该   提供某种钩子......如果不是它的设计是坏的!

  4.         

    我的回答主要包括用例2

答案 1 :(得分:32)

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

SCENARIO 2 - while while循环:然而,作为计时机制(而+ Thread.Sleep)对99%的应用程序完全没问题,这些应用程序不需要确切地知道 当被阻塞的线程应该“唤醒*。创建线程需要200k个周期的参数也是无效的 - 无论如何都需要创建时序循环线程,200k周期只是另一个大数字(告诉我有多少个周期)打开文件/ socket / db调用?)。

因此,如果在+ Thread.Sleep工作时,为什么会复杂化?只有语法律师会实践

答案 2 :(得分:7)

我想从编码政治的角度回答这个问题,这对任何人都有帮助,也可能没有帮助。但是,特别是当您处理专为9-5企业程序员设计的工具时,编写文档的人倾向于使用类似“#34;不应该"并且"从不"除非你真的知道自己在做什么以及为什么要这样做,否则不要这样做。

我在C#世界中的另外几个最喜欢的是他们告诉你"永远不会叫锁(这个)"或者"永远不要调用GC.Collect()"。这两个在许多博客和官方文档中被强制声明,IMO是完全错误的信息。在某种程度上,这种错误信息是出于其目的,因为它使得初学者在完全研究替代方案之前不会做他们不了解的事情,但同时,它很难通过搜索引擎找到真实的信息。所有这些似乎都指向文章告诉你不要做任何事情,而不回答问题"为什么不呢?"

在政治上,它归结为人们的考虑和好的设计"或者"糟糕的设计"。官方文档不应该指示我的应用程序的设计。如果真的有技术原因你不应该打电话给sleep(),那么IMO文档应该说明在特定场景下调用它是完全可以的,但是可能提供一些与场景无关的替代解决方案或更适合其他情况。

清楚地呼唤"睡眠()"在实际时间条件下明确定义截止日期的许多情况下都很有用,但是,在开始将sleep()放入代码并投掷之前,有更复杂的系统可用于等待和发信号通知应该考虑和理解的线程。你的代码中不必要的sleep()语句通常被认为是初学者'战术。

答案 3 :(得分:3)

这是人们提醒的示例的1).spinning和2).polling循环,而不是Thread.Sleep()部分。 我认为通常会添加Thread.Sleep()以轻松改进正在旋转或轮询循环的代码,因此它只与“坏”代码相关联。

此外,人们会这样做:

while(inWait)Thread.Sleep(5000); 

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

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

正如Andreas N.所提到的,请阅读Threading in C#, by Joe Albahari,这真的很棒。

答案 4 :(得分:3)

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

答案 5 :(得分:3)

我有一个用例,我在这里不太了解,并且会争辩说这是使用Thread.Sleep()的正当理由:

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

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

答案 6 :(得分:-5)

我同意这里的许多人,但我也认为这取决于。

最近我做了这段代码:

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

我的结论是,如果能胜任,请使用它。

答案 7 :(得分:-7)

对于那些在SCENARIO 2中没有看到过反对使用Thread.Sleep的有效参数的人来说,确实有一个 - 应用程序出口被while循环阻止 (SCENARIO 1/3只是简单的愚蠢所以不值得更多提及)

许多人假装是知情的,尖叫的Thread.Sleep是邪恶的,没有提到我们这些要求实际理由不使用它的人的一个正当理由 - 但在这里,感谢皮特 - 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;
    }