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
我不明白为什么第一个元素的管理在获取后不会立即被提取。在有效地拉动数据的序列和传递给观察者的数据之间有一秒。我在这里遗漏了什么或是预期的行为吗?如果有,是否有办法让观察者立即对新值做出反应?
答案 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));
作为旁注,尝试在迭代时更喜欢不可变类型而不是改变值。
*请提供自动工作代码片段,以便人们可以更好地回答您的问题。 : - )