Почему я получаю недетерминированные результаты с помощью Repeat () - PullRequest
2 голосов
/ 12 ноября 2011

Я пытаюсь расширить свои знания по Rx.Так что я просто играю с потоками и пытаюсь заставить их вести себя так, как я ожидаю.

Хотя я читал, что у оператора Repeat () есть трудности на практике, потому что вы можете потерять уведомления междуOnCompleted и повторная подписка, я не могу понять, почему происходит следующее:

        var subject = new Subject<string>();

        var my = subject
            .Take(1)
            .Merge(Observable.Empty<string>().Delay(TimeSpan.FromMilliseconds(2000)))
            .Repeat();
        my.Subscribe(Console.WriteLine);

        var stopwatch = new Stopwatch();
        stopwatch.Start();
        Scheduler.ThreadPool.Schedule(TimeSpan.FromSeconds(1), () => subject.OnNext("1 at " + stopwatch.ElapsedMilliseconds));
        Scheduler.ThreadPool.Schedule(TimeSpan.FromSeconds(2), () => subject.OnNext("2 at " + stopwatch.ElapsedMilliseconds));
        Scheduler.ThreadPool.Schedule(TimeSpan.FromSeconds(3), () => subject.OnNext("3 at " + stopwatch.ElapsedMilliseconds));
        Scheduler.ThreadPool.Schedule(TimeSpan.FromSeconds(4), () => subject.OnNext("4 at " + stopwatch.ElapsedMilliseconds));
        Scheduler.ThreadPool.Schedule(TimeSpan.FromSeconds(5), () => subject.OnNext("5 at " + stopwatch.ElapsedMilliseconds));
        Scheduler.ThreadPool.Schedule(TimeSpan.FromSeconds(6), () => subject.OnNext("6 at " + stopwatch.ElapsedMilliseconds));

        Console.ReadLine();

Когда я запускаю этот пример, результаты полностью недетерминированы:

Результат 1:

1 at 1006
3 at 3007
5 at 4995

Хорошо, что он пропустил 2 и 4, но даже внутри этого результата есть некоторая странность, потому что на самом деле между 3 и 5 нет реального 2-секундного разрыва.

Однако результаты могут быть еще хуже.Смотрите это:

1 at 1003
2 at 2003
4 at 4005
6 at 6004

Между 1 и 2 интервалом в 2 секунды нет. Это ровно одна секунда.Почему он не оставил это?

Если бы кто-то мог прояснить для меня вещи, я был бы более чем счастлив!

РЕДАКТИРОВАТЬ

Iпросто заметил, что это может быть Слияние, которое здесь не так.Если я проведу рефакторинг моего запроса в Concat, то все произойдет так, как должно:

        var my = subject
            .Take(1)
            .Concat(Observable.Empty<string>().Delay(TimeSpan.FromMilliseconds(2000)))
            .Repeat();

Ответы [ 2 ]

1 голос
/ 12 ноября 2011

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

Вот как работает ваша оригинальная последовательность:

  • время ~ 0
    • Take(1) подписывается на subject
    • таймер для отложенной пустой наблюдаемой начинает тикать
  • время ~ 1
    • 1 добавляется к subject, 1 записывается; после этого момента никто не подписывается на subject больше
  • время ~ 2
    • таймер для отложенной пустой наблюдаемой истекает. Из-за этого Take(1) снова подписывается на subject, и запускается другой таймер для отложенных пустых наблюдаемых запусков
    • примерно в то же время, 2 добавляется к subject

Из-за небольших различий во времени два действия во времени ок. 2 может произойти в любом порядке. И порядок имеет значение, либо 2 добавляется до повторной подписки Take() или до. И, таким образом, 2 можно записать или нет.

Если вам нужна последовательность, подобная этой:

  1. дождаться первого пункта и вернуть его
  2. подождите около двух секунд
  3. дождаться второго элемента и вернуть его (игнорируя все, что было добавлено во время двухсекундного ожидания)
  4. ...

тогда я думаю, что код в вашем редакторе правильный.

Но это никоим образом не гарантирует детерминированные результаты. На моем компьютере, если я изменяю время ожидания задержанной пустой наблюдаемой на 1960 мс, я получаю недетерминированные результаты при использовании Concat().

0 голосов
/ 24 апреля 2013

Просто чтобы добавить свой 2с в конце на вечеринку;если вы ищете детерминизм для тестирования, то вам следует использовать TestScheduler вместо реальных планировщиков ThreadPool / TaskPool / NewThread.Чисто по причинам, на которые указывает Свик (планирование ОС раскачивает лодку).

...