为什么Thread.Sleep如此有害

Bur*_*imi 117 c# multithreading sleep

我经常看到它提到Thread.Sleep();不应该使用,但我不明白为什么会这样.如果Thread.Sleep();可能造成麻烦,是否有任何替代解决方案具有相同的安全性?

例如.

while(true)
{
    doSomework();
    i++;
    Thread.Sleep(5000);
}
Run Code Online (Sandbox Code Playgroud)

另一个是:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

And*_*air 153

与通话的问题Thread.Sleep相当简洁解释在这里:

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

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

我们有两种不同的用例:

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

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

我的回答主要包括用例2

  • 考虑到今天的硬件,我不会称1 MB的内存是一个巨大的浪费 (26认同)
  • @Default嘿,与原作者讨论:)并且,它总是取决于你的代码 - 或更好:因素......主要问题是"线程是有限的资源" - 今天的孩子们不太了解关于某些实现的效率和成本,因为"硬件很便宜"......但有时你需要编写非常的代码 (13认同)
  • "这会让任何等待的线程成为一个巨大的浪费"呃?如果某些协议规范要求在继续之前暂停一秒钟,那么等待1秒钟会是什么?某个地方的某个线程将不得不等待!线程创建/销毁的开销通常是无关紧要的,因为由于其他原因必须引发线程,并且它在进程的生命周期内运行.我有兴趣看到任何一种避免上下文切换的方法,当规范说"打开泵后,等待至少十秒钟以使压力在打开进料阀之前稳定". (9认同)
  • @CodyGray - 我再次阅读了这篇文章.我的评论中没有看到任何颜色的鱼.Andreas退出网络:'Thread.Sleep有它的用途:在MTA线程上测试/调试时模拟冗长的操作.在.NET中,没有其他理由可以使用它.我认为有许多应用程序,其中sleep()调用就是所需要的.如果开发人员的leigons,(因为有很多),坚持使用sleep()循环作为条件监视器,应该用事件/ condvars/semas/what替换,这是没有理由断言"没有其他理由使用它". (8认同)
  • 在30年的多线程应用程序开发(主要是C++/Delphi/Windows)中,我从未在任何可交付的代码中看到任何睡眠(0)或睡眠(1)循环的需求.偶尔,我已经推出了这样的代码用于调试目的,但它从来没有通过客户."如果你正在编写一些不能完全控制每个线程的东西" - 线程的微观管理与开发人员的微观管理一样大.线程管理就是操作系统的用途 - 应该使用它提供的工具. (8认同)
  • 是的,如果协议规范要求等待,那么旋转和等待可能是正确的做法.但一般来说,情况并非如此.这是一个红鲱鱼论点. (3认同)
  • 当你说"Thread.Sleep:Thread.Sleep(0)有一个不间断的用途时,你链接到的文章是不准确的.这告诉系统你要放弃线程的其余时间片,让另一个,等待,线程运行." `Thread.Sleep(0)`只会放弃相同或更高优先级的线程,需要`Thread.Sleep(1)`来放弃任何等待的线程.在单CPU机器上,对于调用只是'Thread.Sleep(0)`的东西来说,这可能是一个短暂的紧急旋转,等待3个等待它等待被提升的低优先级线程. (2认同)

Swa*_*Jat 32

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

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

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


Jas*_*son 8

我想从编码政治的角度来回答这个问题,这可能对任何人都有帮助,也可能没有帮助.但是,当您处理专为9-5企业程序员设计的工具时,编写文档的人倾向于使用"不应该"和"从不"这样的词来表示"不要这样做,除非你真的知道你是什么'做和为什么'.

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

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

显然,调用"sleep()"在许多情况下很有用,因为在实际时间条件下明确定义了截止日期,但是,在开始抛出睡眠之前,有更多复杂的系统可供等待和发信号通知应该被考虑和理解(进入你的代码,并在你的代码中抛出不必要的sleep()语句通常被认为是初学者的策略.


mik*_*ike 6

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

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

while(inWait)Thread.Sleep(5000); 
Run Code Online (Sandbox Code Playgroud)

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

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

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


小智 5

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


Hen*_*sen 5

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

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

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