4

在我的 .NET 应用程序中,我必须重放一系列传感器事件。所以我创建了一个线程来触发这些事件(通常大约每 1-4 毫秒)。我在这个线程中实现了一个循环,并用来Thread.Sleep(...)让线程在事件之间休眠。

基本上它看起来像这样:

void RunThread() {
  var iter = GetAllEvents().GetEnumerator();
  if (!iter.MoveNext()) {
    return;
  }

  DateTime lastEventTime = iter.Current.Timestamp;
  FireEvent(iter.Current);

  while (iter.MoveNext()) {
    MyEvent nextEvent = iter.Current;

    int timeout = (int)(nextEvent.Timestamp - lastEventTime).TotalMilliseconds; 
    Thread.Sleep(timeout);

    FireEvent(nextEvent);
    lastEventTime = nextEvent.Timestamp;
  }
}

现在,我的问题是Thread.Sleep()有时会遵守指定的超时时间,有时则不会。

我添加了一个检查(使用StopWatch,在上面的代码中不可见)关于睡眠实际花费了多长时间。以下是一些结果:

Expected timeout of 1 ms but got 15 ms.
Expected timeout of 2 ms but got 13 ms.
Expected timeout of 3 ms but got 15 ms.
Expected timeout of 2 ms but got 13 ms.
Expected timeout of 1 ms but got 13 ms.
Expected timeout of 1 ms but got 15 ms.
Expected timeout of 2 ms but got 13 ms.
Expected timeout of 2 ms but got 40 ms.

为什么Thread.Sleep()行为“随机”?

笔记:

  • 此线程运行时,Window 任务管理器不显示任何 cpu 使用情况。
  • 行为随机变化,但至少持续几秒钟。例如,有一次我启动线程时,它运行良好。下一次它一直运行缓慢。下一次它快速运行几秒钟,然后减速或相反。

更新:这是一些显示Sleep行为方式的伪代码:

bool ChooseRespectTimeout() {
  if (this.notYetChosen) {
    this.respectTimeout = ChooseRandomly()
    this.notYetChosen = false
    reset this.notYetChosen after random time period
  }
  return this.respectTimeout
}

void Sleep(int timeout) {
  if (ChooseRespectTimeout())
    Thread.Sleep(timeout)
  else
    Thread.Sleep(timeout * 10)
}
4

7 回答 7

7

Thread.Sleep真的只是调用 win32 Sleep 函数,其文档详细说明了不准确之处。

真正做的是在大约指定的毫秒内Thread.Sleep放弃控制。睡眠功能的细节可能或多或少。操作系统交还控制权的速率由系统时间片决定,即 10-15 毫秒。所以,实际上只能精确到最近的量子。但是,正如其他人指出的那样,如果 CPU 处于重负载下,时间会更长。另外,如果您的线程的优先级低于所有其他线程,它可能没有时间。Thread.Sleep

在负载方面,它与可能发生切换时的负载有关。您说的是 1ms 超时(实际上大约是 15,正如您所见,由于系统量子或计时器的准确性),这意味着操作系统实际上只需要忙 15-30 毫秒就可以了太忙了,无法将控制权交还给线程,直到未来的量子。这不是很多时间。如果在这 15-40 毫秒内有更高优先级的东西需要 CPU,则更高优先级的线程会获得时间片,可能会使放弃其控制权的线程饿死。

真正的意思(对于Thread.Sleep大于 1 的超时值)是它告诉操作系统该线程的优先级低于系统中的所有其他线程,至少在超时值内,直到线程重新获得控制权。 Thread.Sleep不是一种计时机制,它是一种放弃控制的手段。他们真的应该将“睡眠”重命名为“产量”。

如果你想设计一些定期做某事的东西,请使用计时器。如果您需要非常精确,请使用多媒体计时器。

于 2012-09-07T17:05:16.257 回答
3

Windows 不是实时操作系统。

调度程序可能会等待比请求的睡眠持续时间更长的时间来执行一个线程,尤其是在另一个线程仍然很忙的情况下。

如果您想要更高的可靠性,请尝试使用多媒体计时器。这里有一个 .NET 包装器。

于 2012-09-07T15:40:30.097 回答
2

您的程序不能保证是 CPU 的唯一用户。

其他进程获得 CPU 切片 -Thread.Sleep将尽最大努力在超时后恢复,但如果 CPU 忙,则必须等待。

于 2012-09-07T15:38:15.247 回答
2

Thread.Sleep仅保证线程将至少休眠指定的时间间隔。它不提供准确性保证——实际上它不能这样做,因为它是 OS 调度程序决定何时应该为每个阻塞线程提供 CPU 时间。

如果线程可以要求它们在特定时间点再次运行,那么抢占式多任务处理将是不可能的。

于 2012-09-07T15:38:26.147 回答
2

Windows 不是实时操作系统,因此您不会得到这些保证。基本上,Thread.Sleep至少会休眠 几毫秒,但它可能会更长,尤其是对于非常小的值。

于 2012-09-07T15:38:36.607 回答
2

Thread.Sleep()不精确。你给它的数字是最小的睡眠时间,而不是确切的睡眠时间。此外,Thread.Sleep 也有一个最小粒度,我认为是 10ms IIRC 左右。

如果您需要更精确的计时,那么您可能想要使用计时器。但是,请注意,尤其是 Windows 和 .NET 不是“实时”的,因此它们无法为您提供精确的准确性。

例如,如果垃圾收集器启动,处理计时器可能需要更长的时间。

于 2012-09-07T15:38:36.643 回答
0

您不是最近唯一指出这一点的人。自 XP 以来,一定发生了一些变化。Sleep() 然后在有用的时间间隔内非常可靠,并且确实准确,例如。秒或分钟。正如其他人指出的那样,4ms 完全超出了 Sleep() 可用的时间。

现在看来,随着后来的操作系统,一些额外的不确定性不知何故潜入了,尽管我自己还没有测试过。

在有用的长时间内,您应该提高睡眠线程的优先级,以便它在睡眠间隔到期时准备好后立即被调度。

对于 4 毫秒,请查看多媒体计时器,如科幻小说 (+1) 所建议的那样。

于 2012-09-07T16:56:40.957 回答