20

首先,我没有问与C# 相同的问题 - Thread.Sleep 的替代方案?,或替代 C# 中的 Thread.Sleep?. 我认为我没有错误地使用它,并且需要针对特定​​情况的真正替代品。

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

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

这种违规导致了​​Peter Richie 的文章,关于为什么这构成了糟糕的设计。

我们都知道线程创建是昂贵的,线程中的阻塞意味着池上的争用。我们也知道每个线程将分配一个兆的内存,所以它应该有很短的生命周期,在 UI 上阻塞是邪恶的,使用睡眠来计时是不可靠的等等等等等等。这让我明白了,如果你真的需要执行睡眠,如果不是 Thread.Sleep,你应该使用什么?

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

我在使用 Thread.Sleep 非常成功的生产代码中有以下情况:

  • 等待操作系统释放文件锁(遇到文件锁问题,稍等片刻,再试一次,过一会儿放弃)。
  • 杀死一个进程并等待它不在进程列表中出现(杀死它,检查它没有运行,等待一秒钟,检查它没有运行,强制关闭)。
  • 等待复制缓冲区刷新(检查文件大小,尝试访问它,等待,检查大小是否已更改)。

如果在这种情况下不使用 Thread.Sleep,我还有哪些其他选择?紧密的循环往往会使事情变得更糟,我不认为这会使其使用成为“设计缺陷”,尤其是因为 UI 上没有任何内容,并且仅在后台线程中。在多线程环境中等待其他事情,外部因素会影响您的代码,这只是软件的本质,有时您需要等待......

4

3 回答 3

15

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

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

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

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

我对您要问的确切内容没有很好的感觉,因此我不会对此进行详细说明。如果您发表评论,我可以扩展我的答案。


(*)轮询等待的理论原因如下:

假设我有如下代码:

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

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

  • 线程唤醒并检查条件(调用Done()T/D次数
  • START从到的持续时间STOP包括一个预期的D/2纯粹因为Thread.Sleep

D你应该选择什么值?随着您的增加D,预期的持续时间STARTSTOP线性增加。随着您的减少D,(限制在)迭代次数增加为1/D。这两个都是不好的,找到正确D的是有问题的。

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

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

从理论上讲,只要以WaitDone()某种方式神奇地等待直到操作完成但不再等待,在等待轮询案例中发现的两个问题都消失了:这个线程等待的时间恰好是正确的——不多也不少!

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

于 2013-06-07T21:07:45.293 回答
2

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

你也明白阻塞 UI 线程是不好的。

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

所以,直接回答你的问题“这让我明白了,如果你真的需要执行睡眠,如果不是 Thread.Sleep,你应该使用什么?”,在一个设计良好的现代程序中,你永远不需要做它,您只需为后者安排一个任务。

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

转到您的示例,您对命令式编程范式的参与度有点高。

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

等待同步机制并不是“坏事”。

于 2013-06-07T23:46:25.443 回答
0

在我的一个项目中,我使用了 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);
    }

编辑:

正如 Darky711 推荐的那样,更好的方法是:

Task.Delay(1000).Wait();
于 2017-05-16T20:40:06.620 回答