使用Thread.Sleep进行等待的替代方法

时间:2013-06-07 20:59:37

标签: c# multithreading wait thread-sleep

首先,我不是问C# - Alternative to Thread.Sleep?Alternative to Thread.Sleep in C#?相同的问题。我认为我没有错误地使用它,并且需要在特定情况下使用真正的替代方案。

在代码分析运行期间,我看到了一个令人惊讶的违规行为:

  

Thread.Sleep()的使用是设计缺陷的标志。

此违规导致Peter Richie's article导致为什么这会构成糟糕的设计。

我们都知道线程创建很昂贵,线程中的阻塞意味着池上的争用。我们也知道每个线程将分配一个兆内存,因此它应该有一个短的寿命,在UI上阻塞是邪恶的,使用睡眠时间是不可靠的等等。这导致我的观点,如果你真的需要执行睡觉,如果不是Thread.Sleep,你应该使用什么?

Peter继续提到零睡眠是Thread.Sleep的唯一正确用法,有效地放弃了线程的时间片并允许其他线程处理。然后更可怕的是,这仅仅是因为非托管线程的限制,如果在CLR中重新实现将产生在应用程序中使用Thread.Sleep的副作用。事实上,常见的不良用法的所有要点都是不良用法的好例子。

我在使用Thread.Sleep的生产代码中遇到以下情况:

  • 等待文件锁被操作系统放弃(捕获文件锁定问题,等待一秒,再试一次,稍后放弃)。
  • 杀死进程并等待它不显示在进程列表中(终止它,检查它没有运行,等待一秒,检查它是否仍在运行,强行关闭)。
  • 等待复制缓冲区刷新(检查文件大小,尝试访问它,等待,检查大小是否已更改)。

在这种情况下不使用Thread.Sleep,我还有其他选择吗?紧密的循环往往会使事情变得更糟,我不相信这会使它的使用成为“设计缺陷”,尤其是因为UI上没有任何东西,只有后台线程。在多线程环境中等待其他事情的软件本质就是影响代码的外部因素,有时你需要等待......

3 个答案:

答案 0 :(得分:11)

WaitHandle类型和派生类型提供了一种事件驱动机制,用于等待与操作系统的连接。例如,当您有Task<T> task并且通过访问task.Result等待结果时,内部实现不会轮询Thread.Sleep之间的调用。它使用WaitHandle派生类型来进行等待和同步。

有时,基于轮询的方法是必要的,就像您在项目符号列表中提供的一些示例一样,但通常可以使用事件驱动的方法。并不是Thread.Sleep 总是 不好 - 只是经常被滥用

  

在多线程环境中等待其他事情的软件本质就是影响代码的外部因素,有时你需要等待......

等待 很好。 等待投票 通常不是(*)。如果您有任何方法可以使用 事件驱动的等待 ,您通常应该努力使用它。

我对你正在询问的具体内容并不十分了解,所以我不会详细说明。如果你发表评论,我可以扩大我的答案。


(*) 等待投票 的理论原因不好如下:

假设我的代码如下所示:

//START
Begin();
while (!Done())
    Thread.Sleep(D);
//STOP

Begin()开始一些操作。 Done()返回true表示操作已完成。假设这将在大约T时间后发生。然后:

  • 线程醒来并检查条件(调用Done()T/D
  • STARTSTOP的持续时间包含预期的D/2,纯粹是因为Thread.Sleep

您应该选择D的价值?当您增加D时,STARTSTOP的预期持续时间会线性增加。当您减少D时,(绑定)迭代次数将增加为1/D。这两个都很糟糕,找到正确的D是有问题的。

现在将其与 事件驱动的等待 进行比较:

//START
Begin();
WaitDone();
//STOP

从理论上讲,只要WaitDone()以某种方式神奇地等待操作完成但不再等待, 在轮询中等待的两个问题 案例已经消失:这个帖子等待了恰当的时间 - 不多也不少!

重申我开始的观点:在.NET中,WaitHandle类和派生类型是促进这种方法的原因。

答案 1 :(得分:2)

嗯,你说的大部分都是。引用“我们都知道线程创建是昂贵的,并且线程中的阻塞意味着池上的争用”,因此您甚至可以理解使用线程池的内容。

您也明白阻止UI线程是不好的。

再次查看线程池模型:您有一个线程池,可能每个处理器一个,然后将任务传递给它们。它会阻止其中一个线程有什么意义?如果现在没有工作要做,那么它应该只是进行一项不同的任务。

所以,直接回答你的问题“如果你真的需要进行睡眠,那么你应该使用什么,如果不是Thread.Sleep?”,在现代精心设计的程序中,你永远不会需要这样做,你只需为后者安排任务。

您应该将池中的线程(与系统中的处理器一样)视为资源,在不需要时应将其释放给其他人。

回到你的例子,你有点过分参与命令式编程范式。

  • 你不需要等待一个过程消失......我不知道为什么你需要这个,但是如果必须等待,那是因为你有时间工作,你的“继续”功能。你应该为这个“继续”设置一个计时器。
  • 文件示例应该有其他机制,如果它们没有....这将是良好的操作系统设计。例如,等待缓冲区刷新的唯一安全方法是操作系统原语,就像fsync一样。
  • 如果有人写入文件然后从文件中读取另一个文件,则需要同步机制,而不是定时等待(除非文件仅附加,在这种情况下文件本身是同步机制)

等待同步机制并不“糟糕”。

答案 2 :(得分:-1)

在我的一个项目中,我使用了2个线程,我遇到了问题,将UI冻结thnx到Thread.Sleep ....这解决了我的问题:

    public static void Sleeping(int miliseconds)
    {
        var task = Sleep(miliseconds);
        task.Wait();
    }

    public static async Task Sleep(int miliseconds)
    {
        await Task.Delay(miliseconds);
    }