VB.net Несогласованный таймер сна потока

Итак, я играл с VB.net и придумывал способы надежного запуска потока каждые 60 секунд, независимо от того, сколько времени потребовалось предыдущему потоку для выполнения своей работы. Вот мой вопрос. Учитывая следующий код:

    Dim intTemp As Integer
    intTemp = 2
    Do While intTemp > 1
        Dim objWriter As New System.IO.StreamWriter("C:\Documents\Visual Studio 2010\Projects\Report\Report\Stream.txt", True)
        intTemp = intTemp + 1
        System.Threading.Thread.Sleep(5000)

        objWriter.Write(intTemp & " " & Date.Now & " " & Date.Now.Millisecond & vbCrLf)
        objWriter.Close()
    Loop

Создает это в файле stream.txt.

3 4/5/2011 9:41:27 AM 807
4 4/5/2011 9:41:32 AM 812
5 4/5/2011 9:41:37 AM 817
6 4/5/2011 9:41:42 AM 822
7 4/5/2011 9:41:47 AM 826
8 4/5/2011 9:41:52 AM 831
9 4/5/2011 9:41:57 AM 836
10 4/5/2011 9:42:02 AM 841
11 4/5/2011 9:42:07 AM 799

Мое предположение для этого вывода будет заключаться в том, что время между каждой строкой должно составлять ровно 5000 миллисекунд плюс время, необходимое для выполнения остальной части цикла, которое может варьироваться, учитывая, что может быть неизвестная задержка из-за дискового ввода-вывода. Моя проблема в том, что просмотр строк 10 и 11 и вычитание дает мне разницу в 4958 миллисекунд. Так что мой вопрос, что, черт возьми, происходит там? Как можно получить разницу менее 5000 миллисекунд, когда я сказал потоку спать в течение 5000 миллисекунд перед завершением процесса. Что мне не хватает?


person Jarred Masterson    schedule 05.04.2011    source источник
comment
Я предполагаю, что ОС запланировала повторный запуск потока незадолго до истечения 5 секунд. Звучит не так уж плохо.   -  person    schedule 06.04.2011
comment
Правильно, меня беспокоит только время, так как я буду собирать данные из внешнего источника, который также рассчитан по времени. Если в конце концов мой таймер сдвинется, я рискую потерять точки данных. Вы правы в том, что кажется, что он выполняет то, о чем его просят, в более широком смысле, даже если индивидуальные различия во времени не идеальны.   -  person Jarred Masterson    schedule 06.04.2011
comment
Thread.Sleep обычно не подходит для задачи синхронизации в целом. Обратите внимание на Threading.Timer. Это все еще не дает всех гарантий, и может потребоваться более продвинутый подход (ext. lib) или ожидание вращения. Также убедитесь, что время выполнения рассчитывается из x = start + i * m, а не x += m, так как последнее во многих случаях приведет к ошибкам, как в приведенном выше примере. С осторожной формулой всегда можно отрегулировать назад.   -  person    schedule 06.04.2011
comment
Вы должны добавить к своему вопросу тот факт, что этот тест был запущен на виртуальной машине. Я позволил вашему тесту работать в течение 10 минут и не смог воспроизвести результаты в Windows7.   -  person Seth Reno    schedule 06.04.2011
comment
@Seth Reno - Спасибо за совет, и я всегда буду помнить об этом в будущем. Кроме того, большое спасибо за то, что нашли время, чтобы запустить этот тест в течение 10 минут! Конечно, вам не нужно было делать только это во время работы, но вам потребовалось бы некоторое время, чтобы просмотреть результирующий файл. Еще раз спасибо! Это был мой первый вопрос здесь, и все были очень полезны!   -  person Jarred Masterson    schedule 06.04.2011


Ответы (5)


Предложение по реализации: если вам нужна точность синхронизации, вместо Do/Loop внутри потока (с Thread.Sleep) просто используйте экземпляр System.Timers.Timer (это сильно отличается от старого объекта WinForms "Timer" еще до появления .NET). Это позволит вам указать TimeSpan между вызовами метода.

Хотя я не могу ручаться за истинную «точность» между Thread.Sleep и экземпляром Timer (я просто предположил, что Timer будет более точным, учитывая, что хронометраж является его основной функцией)... но, возможно, кто-нибудь мог бы написать быстрый тест ?

person qJake    schedule 05.04.2011
comment
Спасибо! Я рад, что вы сказали это, потому что я уже пошел по этому пути, также полагая, что было бы точнее запустить асинхронный поток из обработчика timer.tick. Я рад, что кто-то еще пришел к такому же выводу! - person Jarred Masterson; 06.04.2011

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

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

Вы можете прочитать об операционных системах реального времени, чтобы получить больше информации.

person Spencer Ruport    schedule 05.04.2011
comment
Тогда было бы интересно спросить: дает ли Thread.Sleep в Windows (скажем, 7/2008) гарантию относительно минимальной продолжительности сна, если он не прерывается? Максимум, конечно, не гарантирован. Я бы предположил, что это было время, переданное Thread.Sleep (например, только округление с превышением ярости). - person ; 06.04.2011
comment
Хотя теоретически я уверен, что существует какая-то минимальная гарантия, я был бы удивлен, если бы она была тщательно задокументирована. Просто за то, что любой, кто полагается на ОС для поддержания каких-либо гарантий на миллисекундном уровне, либо неправильно подходит к проблеме, либо пытается использовать ОС не по назначению. Несмотря на это, это не мешает кому-либо кодировать в каком-то минимуме, поскольку, очевидно, истекшие миллисекунды могут быть определены, и ничто не мешает разработчику вернуть его в спящий режим, если это необходимо. - person Spencer Ruport; 06.04.2011

Возможно, ваши системные часы были обновлены в середине цикла.

person Seth Reno    schedule 05.04.2011
comment
Возможно! Я не думал об этом. Вы можете быть здесь, потому что я разрабатываю виртуальную машину, часы которой должны синхронизироваться гипервизором. Возможно, мне придется запустить этот тест на другом хост-компьютере с ОС, работающей на металле, вместо виртуализации, чтобы увидеть, отличаются ли результаты! - person Jarred Masterson; 06.04.2011

Вы также должны учитывать время, необходимое для создания, открытия и закрытия нового StreamWriter. Если вы сделаете это вне цикла, результаты будут намного ближе к ожидаемым.

Например:

  Dim builder As New Text.StringBuilder()
  For i As Integer = 0 To 10
    Threading.Thread.Sleep(1000)
    builder.AppendLine(String.Format("{0} {1} {2}", i, Now, Now.Millisecond))
  Next
  IO.File.WriteAllText("c:\sleepTest.txt", builder.ToString)

Производит этот вывод:

0 4/5/2011 3:15:35 PM 974
1 4/5/2011 3:15:36 PM 988
2 4/5/2011 3:15:37 PM 988
3 4/5/2011 3:15:38 PM 988
4 4/5/2011 3:15:39 PM 989
5 4/5/2011 3:15:40 PM 989
6 4/5/2011 3:15:41 PM 989
7 4/5/2011 3:15:42 PM 989
8 4/5/2011 3:15:43 PM 989
9 4/5/2011 3:15:44 PM 989
10 4/5/2011 3:15:45 PM 989
person Seth Reno    schedule 05.04.2011
comment
Но не приведет ли это к увеличению времени, а теперь к сокращению времени? - person ; 06.04.2011
comment
Это предполагает, что вы можете сохранить блокировку файла без каких-либо других последствий со стороны других программ, пытающихся получить к нему доступ. - person qJake; 06.04.2011
comment
Согласен со SpikeX здесь. Было бы лучше просто сохранить все эти строки в построителе строк, который занимает очень мало времени на обработку. После завершения цикла откройте файл и запишите все содержимое построителя строк. - person Spencer Ruport; 06.04.2011
comment
Спасибо, я действительно не думал об этом. Я предполагал, что время было связано с тем, что дисковая подсистема ввода-вывода конкурирует за ресурсы, поскольку это самая медленная часть современной системы. Ваш тестовый пример очень интересен, и мне, возможно, придется пересмотреть свои предыдущие предположения! - person Jarred Masterson; 06.04.2011
comment
@Jarred, @Spencer Ruport - изменен код в соответствии с вашими предложениями. - person Seth Reno; 06.04.2011
comment
@pst - да, я совершенно неправильно понял вопрос: P - person Seth Reno; 06.04.2011

Ответ на вашу загадку на самом деле довольно прост. Нет гарантии, что два оператора будут выполняться в один и тот же интервал времени. Поскольку у вас есть несколько вызовов в одном выражении, это еще хуже.

Вполне возможно, что ваш вызов DateTime.Now и DateTime.Now.Millisecond может происходить с разницей почти в секунду (особенно если между ними происходила какая-то сборка мусора).

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

Dim dt as DateTime
dt = DateTime.Now
objWriter.Write(intTemp & " " & dt & " " & dt.Millisecond & vbCrLf)
person Erik Funkenbusch    schedule 05.04.2011
comment
Ах! Я считаю, что вы правы здесь! Я не думал об этом таким образом, я предполагал (очевидно, неправильно), что нахождение в одной строке будет означать, что это произойдет в том же временном интервале, когда оно будет переведено в машинный код. Но я полностью согласен с вашей точкой зрения. - person Jarred Masterson; 06.04.2011