Насколько точна Thread.Sleep(TimeSpan)?

Я столкнулся с unit test, который прерывается с перерывами, потому что прошедшее время не то, что я ожидаю.

Пример того, как выглядит этот тест:

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();

TimeSpan oneSecond = new TimeSpan(0, 0, 1);

for(int i=0; i<3; i++)
{
    Thread.Sleep(oneSecond);
}

stopwatch.Stop();

Assert.GreaterOrEqual(stopwatch.ElapsedMilliseconds, 2999);

В большинстве случаев это проходит, но по какой-то причине он потерпел неудачу, потому что:

Ожидается: больше или равно 2999 Но было: 2998

Я не понимаю, как это может быть меньше 3 секунд. Есть ли проблема с проблемой Thread.Sleep или секундомера, о которой я не знаю?

Также как обновление некоторых из приведенных ниже вопросов. Сценарий, который тестируется модулем, является классом, который позволяет вызвать метод для выполнения какого-либо действия, и если он терпит неудачу, подождите второй и вспомните этот метод. Тест, показанный выше, является просто приближением к тому, что происходит.

Предположим, что я хотел вызвать метод DoSomething()... но в случае исключения из DoSomething(), я хочу, чтобы повторить попытку, вызвав его максимум до 3 раз, но подождите 1 секунду между каждая попытка. Цель unit test в этом случае состоит в том, чтобы проверить, что, когда мы запросили 3 повтора с 1 секундой ожидания между каждой попыткой, общее время, затраченное на большее, чем 3 секунды.

Ответы

Ответ 1

Ваша тема разделяет время процессора с другими потоками. Сон закончится, как только вы вернетесь, и ядро ​​замечает, что время сна истекло, поэтому это не так точно.

Нагрузка на CPU, приоритеты процессов, количество параллельных потоков, даже из других процессов, будут влиять на нее.

Ответ 2

Thread.Sleep не предназначен для точного бодрствования. Действительно, сама архитектура окон не предназначена для такого рода вещей.

Ответ 3

Снабжение нити и выбор времени/дросселирование - это очень разные вещи, и к ним следует относиться надлежащим образом. Спящий поток - общая задача, позволяющая системе давать другим потокам и процессам возможность выполнить, не будучи конкретными. С другой стороны, регулирование приложения или планирование задач, требующих точного времени, должно выполняться с явным таймером.

Имейте в виду, что если вам понадобятся временные процессы или синхронизация, вам будет трудно достичь этого с помощью обычного процесса в окнах. Вам нужно будет использовать приоритеты в реальном времени для Windows, чтобы успешно достичь точного времени или дросселирования, поскольку окна могут скрыть любой поток в любое время, если он выгружен другим потоком.

Ответ 4

Быстро экспериментируя, я замечаю, что фрагмент кода, например...

do {Debug.WriteLine(DateTime.Now.TimeOfDay.TotalMilliseconds.ToString()); } while (1);

отображает одно и то же число несколько раз, затем перескакивает на новый номер, отображаемый несколько раз и т.д. Разрыв между этими наборами чисел составляет 15,625 мс, который я замечаю 1000/64.

Похоже, что таймеры Windows имеют степень детализации 1/64 секунды. Если вам нужно лучше, чем тогда, я почувствую вашу боль, но это рамки, в которые вы должны вписаться. (Windows не является жесткой оперативной ОС и не претендует на это).

Ответ 5

В одном приложении, где я хотел спать по меньшей мере за миллисекунды, я использовал некоторый код, похожий на:

public void Sleep(int milliseconds)
{
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();

    while (stopwatch.ElapsedMilliseconds < milliseconds)
    {
        int timeout = milliseconds - stopwatch.ElapsedMilliseconds;
        Thread.Sleep(timeout >= 0 ? timeout : 0);
    }

    stopwatch.Stop();
}

В ответ на то, насколько точна Thread.Sleep, она не совсем точная. Я думаю, что разрешение составляет около 10 мс. Не гарантируется выполнение большей части всего, кроме "приблизительно" этого долгого времени.

Ответ 6

Возможно, вы не полагаетесь на временную предвзятость, чтобы узнать, успешно ли вы. Лучше подсчитайте количество попыток и оцените это:

int tries;

for(tries=0; tries<3; tries++)
{
    Thread.Sleep(oneSecond);
}

Assert.GreaterOrEqual(tries, 3);