我经常看到它提到不应使用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()”。

我不会说这是有害的。我宁愿说这就像goto:也就是说,可能比Sleep更好地解决您的问题。

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

@KevinKostlan使用计时器或调度框架(如quartz.net)然后...

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

#1 楼

调用Thread.Sleep的问题在这里得到了很简洁的解释:


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.Sleep一段时间-construct(演示和答案,不错的博客条目)

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


我们有2种不同的用例:


我们正在等待,因为我们知道应该继续的特定时间跨度(使用Thread.SleepSystem.Threading.Timer等)
我们正在等待,因为条件会改变一段时间...
关键词会出现一段时间!如果条件检查在我们的代码域中,我们
应使用WaitHandles-否则外部组件应提供某种挂钩……如果不是这样,则其设计不好!

我的回答主要涉及用例2


评论


考虑到当今的硬件,我不会将1 MB的内存称为巨大的浪费

–默认
2012年1月11日在8:07

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

–安德烈亚斯·尼德米尔(Andreas Niedermair)
2012年1月11日在8:08



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

–马丁·詹姆斯(Martin James)
2012年1月11日上午9:07

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

–马丁·詹姆斯(Martin James)
2012年1月11日上午9:34

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

–马丁·詹姆斯(Martin James)
2012年1月12日上午1:01

#2 楼

场景1-等待异步任务完成:我同意在线程正在等待另一个线程上的任务完成的情况下使用WaitHandle / Auto | ManualResetEvent。场景2-定时循环:但是,由于粗略的计时机制(while + Thread.Sleep)对于99%的应用程序来说是完美的,它不需要确切知道被阻塞的线程何时应该“唤醒*”。创建线程需要200k个周期的论点是同样无效-仍然需要创建定时循环线程,而200k个周期只是另一个大数字(告诉我打开文件/套接字/ db调用需要多少个周期?)。

如果while + Thread睡眠有效,为什么使事情复杂化?只有语法律师才是切实可行的!

评论


值得庆幸的是,我们现在有了TaskCompletionSource可以有效地等待结果。

–奥斯丁·萨尔加特(Austin Salgat)
18 Mar 27 '18在16:04

#3 楼

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

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

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

在许多情况下(在实时条件下明确定义了截止日期),明确地调用“ sleep()”非常有用,但是,还有一些更复杂的系统在等待和发信号通知线程在开始将sleep()放入代码中并在代码中引发不必要的sleep()语句之前,通常会被理解并理解,这通常被视为初学者的策略。

#4 楼

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

此外,像这样的东西:

while(inWait)Thread.Sleep(5000); 


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

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

正如Andreas N.提到的那样,阅读Joe Albahari的C#Threading,真的非常好。

评论


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

– Shinzou
16年4月15日在20:16

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

– Mike
16-4-16的1:36

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

– Shinzou
16年4月16日在8:26

#5 楼

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

#6 楼

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

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

花费2000-8000个周期在两次调用之间进行上下文切换可能要花费500毫秒才能执行,这与1线程的堆栈MB,在服务器上作为单个实例运行。

评论


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

– Theodor Zoulias
5月12日12:36

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


评论


-,不是,Thread.Sleep并不是这样做的原因(新的线程和持续的while循环是)!您可以只删除Thread.Sleep-line-等一下:该程序也不会退出...

–安德烈亚斯·尼德米尔(Andreas Niedermair)
13年2月20日在9:54