Rx调度/阻塞问题:订阅的OnNext调用未在定时器序列

时间:2016-11-02 13:35:20

标签: system.reactive

我完全不知道这件事。只在Rx工作了几天而且很明显我的理解中存在一个基本的例外。所以在根据各种文章尝试了很多想法后,我需要一些专家的眼睛来指出我是什么&#39我没有看到......

我正在尝试测试一些模拟轮询机制的代码。我正在使用Microsoft.Reactive.Testing TestScheduler来提供一个"虚拟时间线"我可以推进时间表,以便在很长一段时间内快速模拟大量民意调查......确实非常有用:

var scheduler = new TestScheduler();

// create the sequence of polls (each poll returns a string)
var observeable = poller.CreatePollingSequence(scheduler);

observeable.Subscribe(
           item =>
           {
                Debug.WriteLine(item));

           },
           _ => Debug.WriteLine("Completed!"));

scheduler.AdvanceBy(60*1000* TimeSpan.TicksPerMillisecond);

这是CreatePollingSequence的实现。 Note" PollResult"是一个简单的类,它包含一个表示轮询返回的行数的字符串列表。例如PollResult(1)生成一个包含一个文本字符串的结果。

   public  IObservable<PollResult> CreatePollingSequence(IScheduler scheduler)
    {

        return Observable.Create<PollResult>( 
            (IObserver<PollResult> observer) => 
            {
                 return  scheduler.ScheduleAsync(async (sched, ct) =>
                  {
                    Observable.Timer(TimeSpan.FromSeconds(0), scheduler)
                       .Subscribe(_ =>
                    {
                            observer.OnNext(new PollResult(1));

                    });

                   await sched.Yield();
                   observer.OnCompleted(); // end of sequence


                   return Disposable.Empty;
                });

            });




    }

所以这一切似乎都运转正常(虽然我不完全确定在我的Rx旅程中此时某些线路的基本原理,甚至整体正确性......)。

但这是关键问题:当我将ObserveableTimer的offsetTime参数更改为非零值时(为了模拟时间 dueOffsetTime 的初始轮询),事情开始误入歧途。具体来说,订阅 CreatePollingSequence 返回的序列的代码突然永远不会执行。要重新迭代, 它会在Timer序列的OffsetTime参数为零时执行 。从代码中可以看出,我已经开始怀疑当Timer返回的序列在除了立即之外的任何其他时间生成时,这可能是调度程序上的某种形式的死锁...因此出现.Yield和使用ScheduleAsync ......但我敏锐地意识到我没有发现实际问题,所以我不能确定解析代码应该是什么。因此这个问题 - 希望有人能指出错误的原因......非常感谢提前。

2 个答案:

答案 0 :(得分:0)

我不完全确定CreatePollingSequence应该做什么,但我认为这样做更简洁,适用于非零offsetTime:

public IObservable<PollResult> CreatePollingSequence(IScheduler scheduler)
{
    return Observable.Timer(TimeSpan.FromSeconds(1), scheduler)
        .Select(_ => new PollResult(1));
}

编辑

在回答您的问题时,您无需明确致电observer.OnNextObservable.Timer会为您做到这一点。我建议您尝试替换我的代码。

Rx包围IObservableIObserver的方式与LINQ包围IEnumerableIEnumerator(或者IQueryable)的方式非常相似。在LINQ-land中,如果你直接调用enumerater.MoveNext(),你可能做错了:LINQ的全部意义是利用Enumerable monad的灵活性/可兼容性,所以你要相对调用高级,可链接的函数(LINQ运算符)。

与Rx类似,目标是尽可能使用Rx运算符,并避免直接调用Observer或调度程序方法。你可以使它工作,维护,阅读,支持等更难。如果我正在审查你的代码,我会引用以下问题:

  • Observable.Create非常类似于实现您的observable,您希望避免这样做。
  • 直接调用调度程序是另一个容易出错的事情。你的代码可能导致死锁,因为它没有屈服。
  • 您订阅了一个observable(Observable.Timer订阅),但没有处置或跟踪该订阅。这可能会导致内存泄漏。

总之,使用简单的代码。它使小猫高兴。

答案 1 :(得分:0)

最后解决了这个问题。我认为!这是一个简单的愚蠢错误。我把observer.OnCompleted()调用在错误的地方,事实上它根本就没有被调用过。它应该是Observeable.Timer.Subscribe的第二个参数(参见下面的代码)。 ScheduleAsync也是多余的。

 public IObservable<PollResult> CreatePollingSequence(IScheduler scheduler)
    {

        return Observable.Create<PollResult>(
            (IObserver<PollResult> observer) =>
            {
                return scheduler.ScheduleAsync(async (sched, ct) =>
                   {


                        Observable.Timer(TimeSpan.FromSeconds(1), scheduler)
                            .Subscribe(_ =>
                            {

                                // make this configurable...i.e actual data returned!!!
                                //    GetData();


                                observer.OnNext(new PollResult(2));



                            }, () => observer.OnCompleted());



                    return Disposable.Empty;
               });

            });




    }