首页 文章

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

提问于
浏览
16

首先,我不是问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非常成功:

  • 等待文件锁被操作系统放弃(捕获文件锁定问题, wait for a second ,再试一次,过一会儿就放弃) .

  • 杀死一个进程并等待它不显示在进程列表中(杀死它,检查它没有运行, wait for a second ,检查它是否仍在运行,强行关闭) .

  • 等待复制缓冲区刷新(检查文件大小,尝试访问它, wait ,检查大小是否已更改) .

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

3 回答

  • 11

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

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

    您还了解阻止UI线程是不好的 .

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

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

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

    在你的例子中,你有点过分参与命令式编程范式 .

    • 你不要想象为什么你需要这个,但如果必须等待,那是因为你有时间工作,你的功能"continuation" . 你应该为这个"continuation"设置一个计时器 .

    • 文件示例应该有其他机制,如果它们没有....这将是良好的操作系统设计 . 例如,等待缓冲区刷新的唯一安全方法是操作系统原语,就像fsync一样 .

    • 如果有人写入文件然后从文件读取另一个文件,则需要同步机制,而不是定时等待(除非文件仅附加,在这种情况下文件本身是同步机制) .

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

  • -1

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

    有时,基于轮询的方法是必要的,就像您在项目符号列表中提供的一些示例一样,但通常您可以使用事件驱动的方法 . 这不是 Thread.Sleepalways 不好 - 只是它是 very often misused .

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

    wait 很好 . 至 wait with polling 通常不是(*) . 如果有任何方法可以使用 event-driven wait ,您通常应该努力使用它 .

    我对你究竟是什么感觉没有很好的感觉,所以我不会详细说明 . 如果您发表评论我可以扩展我的答案 .


    (*)理论上的原因 waiting with polling 不好如下:

    假设我的代码如下所示:

    //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 是有问题的 .

    现在将其与 event-driven wait 进行比较:

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

    从理论上讲,只要 WaitDone() 不知何故神奇地等到操作完成但不再存在, waiting with polling 案例中发现的两个问题都消失了:这个线程等待了恰好合适的时间 - 不多也不少!

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

相关问题