使用Reactive(Database polling with Reactive Extensions)
进行数据库轮询已经有了一个很好的问题我有一个类似的问题,但有一个转折:我需要将前一个结果中的值提供给下一个请求。基本上,我想对此进行调查:
interface ResultSet<T>
{
int? CurrentAsOfHandle {get;}
IList<T> Results {get;}
}
Task<ResultSet<T>> GetNewResultsAsync<T>(int? previousRequestHandle);
这个想法是,这会返回自上一个请求以来的所有新项目
GetNewResultsAsync
CurrentAsOf
作为参数传递给previousRequest
参数GetNewResultsAsync
的下一次调用应该实际发生在前一次基本上,有更好的方法:
return Observable.Create<IMessage>(async (observer, cancellationToken) =>
{
int? currentVersion = null;
while (!cancellationToken.IsCancellationRequested)
{
MessageResultSet messageResultSet = await ReadLatestMessagesAsync(currentVersion);
currentVersion = messageResultSet.CurrentVersionHandle;
foreach (IMessage resultMessage in messageResultSet.Messages)
observer.OnNext(resultMessage);
await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken);
}
});
另请注意,此版本允许在等待下一次迭代时收集messageResultSet
(例如,我想也许我可以使用Scan
将先前的结果集对象传递到下一次迭代)
答案 0 :(得分:1)
您的问题基本上减少到:具有签名的Scan
函数:
IObservable<TAccumulate> Scan<TSource, TAccumulate>(this IObservable<TSource> source,
TAccumulate initialValue, Func<TAccumulate, TSource, TAccumulate> accumulator)
但你需要像
这样的东西IObservable<TAccumulate> Scan<TSource, TAccumulate>(this IObservable<TSource> source,
TAccumulate initialValue, Func<TAccumulate, TSource, IObservable<TAccumulate>> accumulator)
...累加器函数返回一个observable,Scan函数会自动缩小它以传入下一个调用。
这是一个穷人的Scan
功能实现:
public static IObservable<TAccumulate> MyScan<TSource, TAccumulate>(this IObservable<TSource> source,
TAccumulate initialValue, Func<TAccumulate, TSource, TAccumulate> accumulator)
{
return source
.Publish(_source => _source
.Take(1)
.Select(s => accumulator(initialValue, s))
.SelectMany(m => _source.MyScan(m, accumulator).StartWith(m))
);
}
鉴于此,我们可以对其进行一些修改以合并缩减功能:
public static IObservable<TAccumulate> MyObservableScan<TSource, TAccumulate>(this IObservable<TSource> source,
TAccumulate initialValue, Func<TAccumulate, TSource, IObservable<TAccumulate>> accumulator)
{
return source
.Publish(_source => _source
.Take(1)
.Select(s => accumulator(initialValue, s))
.SelectMany(async o => (await o.LastOrDefaultAsync())
.Let(m => _source
.MyObservableScan(m, accumulator)
.StartWith(m)
)
)
.Merge()
);
}
//Wrapper to accommodate easy Task -> Observable transformations
public static IObservable<TAccumulate> MyObservableScan<TSource, TAccumulate>(this IObservable<TSource> source,
TAccumulate initialValue, Func<TAccumulate, TSource, Task<TAccumulate>> accumulator)
{
return source.MyObservableScan(initialValue, (a, s) => Observable.FromAsync(() => accumulator(a, s)));
}
//Function to prevent re-evaluation in functional scenarios
public static U Let<T, U>(this T t, Func<T, U> selector)
{
return selector(t);
}
现在我们有了这个花哨的MyObservableScan
运营商,我们可以相对轻松地解决您的问题:
var o = Observable.Interval(TimeSpan.FromMinutes(1))
.MyObservableScan<long, ResultSet<string>>(null, (r, _) => Methods.GetNewResultsAsync<string>(r?.CurrentAsOfHandle))
请注意,在测试中我注意到如果累加器Task
/ Observable
函数的耗时超过源中的间隔,则observable将终止。我不确定为什么。如果有人能够纠正,那就很有责任。
答案 1 :(得分:0)
我发现Observable.Generate
有一个重载,几乎可以解决问题。主要缺点是它不适用于async
。
public static IObservable<TResult> Generate<TState, TResult>(TState initialState, Func<TState, bool> condition, Func<TState, TState> iterate, Func<TState, TResult> resultSelector, Func<TState, TimeSpan> timeSelector, IScheduler scheduler);
我传入null
作为我的初始状态。作为我的条件传递x => true
(无休止地进行投票)。在iterate
内部,我根据传入的状态进行实际轮询。然后在timeSelector
中返回轮询间隔。
所以:
var resultSets = Observable.Generate<ResultSet<IMessage>, IEnumerable<IMessage>>(
//initial (empty) result
new ResultSet<IMessage>(),
//poll endlessly (until unsubscription)
rs => true,
//each polling iteration
rs =>
{
//get the version from the previous result (which could be that initial result)
int? previousVersion = rs.CurrentVersionHandle;
//here's the problem, though: it won't work with async methods :(
MessageResultSet messageResultSet = ReadLatestMessagesAsync(currentVersion).Result;
return messageResultSet;
},
//we only care about spitting out the messages in a result set
rs => rs.Messages,
//polling interval
TimeSpan.FromMinutes(1),
//which scheduler to run each iteration
TaskPoolScheduler.Default);
return resultSets
//project the list of messages into a sequence
.SelectMany(messageList => messageList);