将生成的事件序列创建为冷序列

时间:2014-06-30 10:23:27

标签: c# system.reactive

FWIW - 我在asking for advice on meta

之后以相同方式删除此问题的上一版本,转而使用不同的版本

我有一个包含配置数据的web服务。我想定期调用它Tok以刷新使用它的应用程序中的配置数据。如果服务出错(超时,停机等),我希望保留前一次呼叫的数据,并在不同的时间间隔Tnotok后再次呼叫该服务。最后,我希望这种行为是可测试的。

由于管理时间序列和可测试性似乎是Reactive Extensions的一个重点,我开始使用一个Observable,它将由生成的序列提供。以下是我创建序列的方法:

Observable.Generate<DataProviderResult, DataProviderResult>(
    // we start with some empty data
    new DataProviderResult() { 
            Failures = 0
            , Informations = new List<Information>()},
    // never stop
    (r) => true,
    // there is no iteration
    (r) => r,
    // we get the next value from a call to the webservice
    (r) => FetchNextResults(r),
    // we select time for next msg depending on the current failures
    (r) => r.Failures > 0 ? tnotok : tok,
    // we pass a TestScheduler
    scheduler)
.Suscribe(r => HandleResults(r));

我目前有两个问题:


看起来我正在创建一个热门的观察者。即使尝试使用Publish / Connect,我仍然会在第一个事件中失踪。我怎样才能将它创建为冷可观察物?

myObservable = myObservable.Publish();
myObservable.Suscribe(r => HandleResults(r));
myObservable.Connect() // doesn't call onNext for first element in sequence

当我怀疑时,嫌疑人和世代似乎关闭的顺序,因为对于任何帧,在FetchNextResults方法之前触发了嫌疑人方法。这是正常的吗?我希望序列为帧f调用方法,而不是f + 1。 以下是我用于获取和提取的代码:

private DataProviderResult FetchNextResults(DataProviderResult previousResult)
{
    Console.WriteLine(string.Format("Fetching at {0:hh:mm:ss:fff}", scheduler.Now));
    try
    {
        return new DataProviderResult() { Informations = dataProvider.GetInformation().ToList(), Failures = 0};
    }
    catch (Exception)
    {}
    previousResult.Failures++;

    return previousResult;
}

private void HandleResults(DataProviderResult result)
{
    Console.WriteLine(string.Format("Managing at {0:hh:mm:ss:fff}", scheduler.Now));
    dataResult = result;
}

以下是我所看到的,这促使我阐述了这些问题:

Starting at 12:00:00:000
Fetching at 12:00:00:000 < no managing the result that has been fetched here
Managing at 12:00:01:000 < managing before fetching for frame f
Fetching at 12:00:01:000
Managing at 12:00:02:000
Fetching at 12:00:02:000

编辑:这是一个简单的复制可用程序,用于说明问题。

/*using System;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using Microsoft.Reactive.Testing;*/

private static int fetchData(int i, IScheduler scheduler)
{
  writeTime("fetching " + (i+1).ToString(), scheduler);
  return i+1;
}
private static void manageData(int i, IScheduler scheduler)
{
  writeTime("managing " + i.ToString(), scheduler);
}
private static void writeTime(string msg, IScheduler scheduler)
{
  Console.WriteLine(string.Format("{0:mm:ss:fff} {1}", scheduler.Now, msg));
}

private static void Main(string[] args)
{
    var scheduler = new TestScheduler();
    writeTime("start", scheduler);
    var datas = Observable.Generate<int, int>(fetchData(0, scheduler),
                                                (d) => true,
                                                (d) => fetchData(d, scheduler),
                                                 (d) => d,
                                                 (d) => TimeSpan.FromMilliseconds(1000),
                                                 scheduler)
                                                 .Subscribe(i => manageData(i, scheduler));

    scheduler.AdvanceBy(TimeSpan.FromMilliseconds(3000).Ticks);
}

这输出以下内容:

00:00:000 start
00:00:000 fetching 1
00:01:000 managing 1
00:01:000 fetching 2
00:02:000 managing 2
00:02:000 fetching 3

我不明白为什么第一个元素的管理在获取后不会立即被提取。在有效地拉动数据的序列和传递给观察者的数据之间有一秒。我在这里遗漏了什么或是预期的行为吗?如果有,是否有办法让观察者立即对新值做出反应?

2 个答案:

答案 0 :(得分:2)

您误解了timeSelector参数的用途。每次生成一个值时都会调用它,并返回一个时间,指示在将该值传递给观察者然后生成下一个值之前要延迟多长时间。

这是一种解决问题的非生成方式。

private DataProviderResult FetchNextResult()
{
    // let exceptions throw
    return dataProvider.GetInformation().ToList();
}

private IObservable<DataProviderResult> CreateObservable(IScheduler scheduler)
{
    // an observable that produces a single result then completes
    var fetch = Observable.Defer(
        () => Observable.Return(FetchNextResult));

    // concatenate this observable with one that will pause
    // for "tok" time before completing.
    // This observable will send the result
    // then pause before completing.
    var fetchThenPause = fetch.Concat(Observable
        .Empty<DataProviderResult>()
        .Delay(tok, scheduler));

    // Now, if fetchThenPause fails, we want to consume/ignore the exception
    // and then pause for tnotok time before completing with no results
    var fetchPauseOnErrors = fetchThenPause.Catch(Observable
        .Empty<DataProviderResult>()
        .Delay(tnotok, scheduler));

    // Now, whenever our observable completes (after its pause), start it again.
    var fetchLoop = fetchPauseOnErrors.Repeat();

    // Now use Publish(initialValue) so that we remember the most recent value
    var fetchLoopWithMemory = fetchLoop.Publish(null);

    // YMMV from here on.  Lets use RefCount() to start the
    // connection the first time someone subscribes
    var fetchLoopAuto = fetchLoopWithMemory.RefCount();

    // And lets filter out that first null that will arrive before
    // we ever get the first result from the data provider
    return fetchLoopAuto.Where(t => t != null);
}

public MyClass()
{
    Information = CreateObservable();
}

public IObservable<DataProviderResult> Information { get; private set; }

答案 1 :(得分:1)

Generate产生冷可观察序列,这是我的第一个警钟。

我试图将你的代码拉入linqpad *并运行它并稍微改变它以专注于问题。在我看来,你有Iterator和ResultSelector函数混淆。这些都是背靠背的。迭代时,您应该从上一次迭代中获取值并使用它来生成下一个值。结果选择器用于从您正在迭代的实例中选择值(选择)。

因此,在您的情况下,您要迭代的类型是您想要生成值的类型。因此,将ResultSelector函数保持为标识函数x=>x,并且您的IteratorFunction应该是进行WebService调用的函数。

Observable.Generate<DataProviderResult, DataProviderResult>(
    // we start with some empty data
    new DataProviderResult() { 
            Failures = 0
            , Informations = new List<Information>()},
    // never stop
    (r) => true,
    // we get the next value(iterate) by making a call to the webservice
    (r) => FetchNextResults(r),
    // there is no projection
    (r) => r,
    // we select time for next msg depending on the current failures
    (r) => r.Failures > 0 ? tnotok : tok,
    // we pass a TestScheduler
    scheduler)
.Suscribe(r => HandleResults(r));

作为旁注,尝试在迭代时更喜欢不可变类型而不是改变值。

*请提供自动工作代码片段,以便人们可以更好地回答您的问题。 : - )