使用Thread的替代方法.为等待而睡
本文关键字:等待 方法 Thread 使用 | 更新日期: 2023-09-27 18:17:40
首先,我不是在问与c#相同的问题——Thread.Sleep的替代方案?,或替代线程。在c#中睡觉?我不认为我用错了,在特定的情况下需要一个真正的替代品。
在一次代码分析中,我发现了一个令人惊讶的错误:
使用Thread.Sleep()是设计有缺陷的标志。
这种违反导致了Peter Richie关于为什么这构成糟糕设计的文章。
我们都知道线程创建是昂贵的,线程阻塞意味着池上的争用。我们也知道每个线程将分配一个兆内存,所以它应该有一个短的生命周期,阻塞UI是邪恶的,使用睡眠计时是不可靠的等等。这就引出了我的观点,如果你真的需要执行一个sleep,如果不是Thread.Sleep,你应该使用什么?
Peter继续提到零睡眠是Thread的唯一正确用法。睡眠有效地放弃线程的时间片,并允许其他线程进行处理。更可怕的是,这只是因为对非托管线程的限制,如果在CLR中重新实现,将会产生使用Thread的副作用。在应用程序中休眠。事实上,所有关于常见错误用法的观点都是错误用法的好例子。
我在使用Thread的生产代码中有以下情况。Sleep相当成功:
- 等待操作系统释放文件锁(捕获文件锁问题,等待一秒钟,再试一次,过一段时间后放弃)。
- 杀死一个进程并等待它不在进程列表中出现(杀死它,检查它没有运行,等待一秒钟,检查它还没有运行,强制它关闭)。
- 等待拷贝缓冲区刷新(检查文件大小,尝试访问它,等待,检查大小是否已更改)
不使用线程。在这种情况下睡觉,我还有别的选择吗?紧循环往往会使事情变得更糟,我不认为这使它的使用成为"设计缺陷",特别是因为UI上没有任何东西,只有后台线程。在多线程环境中,等待外部因素影响代码是软件的本质,有时你需要等待……
WaitHandle
类型和派生类型为绑定到操作系统的等待提供了事件驱动的机制。例如,当您有Task<T> task
并且您通过访问task.Result
等待结果时,内部实现不会在Thread.Sleep
调用之间进行轮询。它使用WaitHandle
派生的类型来执行等待和同步。
有时基于轮询的方法是必要的,就像您在项目列表中给出的一些示例一样,但是通常您可以使用事件驱动的方法。这并不是说Thread.Sleep
总是不好,只是它经常被误用。
在多线程环境中等待其他事情是软件的本质,外部因素会影响你的代码,有时你需要等待…
To wait可以。对于等待轮询通常不是(*)。如果有任何方法可以使用事件驱动的等待,您通常应该努力使用它。
我对你的问题没有很好的感觉,所以我不会详细说明这一点。如果你留下评论,我可以扩展我的答案。
(*) 等待轮询不好的理论原因如下:
假设我有如下代码:
//START
Begin();
while (!Done())
Thread.Sleep(D);
//STOP
Begin()
开始操作。Done()
返回true
表示操作已经完成。假设这将在大约T
时间后发生。然后:
- 线程被唤醒并检查状态(调用
Done()
)T/D
次 - 从
START
到STOP
的持续时间包括一个预期的D/2
,纯粹是因为Thread.Sleep
D
的值应该是多少?随着D
的增加,从START
到STOP
的预期持续时间线性增加。当您减少D
时,迭代次数(上限)会随着1/D
的增加而增加。这两个都不好,找到正确的D
是有问题的。
现在将其与事件驱动的等待:
进行比较//START
Begin();
WaitDone();
//STOP
从理论上讲,只要WaitDone()
以某种神奇的方式等待操作完成,而不是再等待,在等待轮询的情况下发现的两个问题都消失了:这个线程等待的时间正好合适——不多也不少!重申我开始的观点:在。net中,WaitHandle
类和派生类型促进了这种方法。
好吧,你说得差不多了。引用"我们都知道线程创建是昂贵的,线程中的阻塞意味着线程池上的争用",所以你甚至明白使用线程池是什么意思。
你也明白阻塞UI线程是不好的。
再看看线程池模型:你有一个线程池,可能每个处理器一个,然后把任务传递给它们。其中一个线程阻塞有什么意义呢?如果它现在没有工作要执行,那么它应该简单地继续执行另一个任务。
所以,直接回到你的问题"这让我想到了我的观点,如果你真的需要执行一个sleep,如果不是Thread.Sleep,你应该使用什么?",在一个现代设计良好的程序中,你永远不需要这样做,你只需要为后者安排一个任务。
应该把池中的线程看作资源,就像系统中的处理器一样,在不需要的时候应该把它们释放给其他人。
回到你的例子,你对命令式编程范式有点太投入了。
- 你不需要等待进程消失…我不知道你为什么需要这个,但如果你必须等待,那是因为你在一段时间后有工作要做,你的函数的"延续"。你应该为这个"延续"设置一个计时器。
- 文件示例应该有其他机制,如果他们没有....这将是一个很好的操作系统设计。例如,等待缓冲区刷新的唯一安全方法是使用OS原语,比如fsync。
- 如果有人写入文件,然后另一个人从文件中读取,那么需要一个同步机制,而不是定时等待(除非文件是只追加的,在这种情况下,文件本身就是同步机制)。
等待同步机制并不是"坏事"。
在我的一个项目中,我使用了2个线程,我遇到了UI冻结线程的问题。睡眠…这解决了我的问题:
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();