Observable.Range重复?

时间:2015-07-20 13:48:31

标签: c# system.reactive reactive-programming

Rx新手 - 我有一个似乎正常运作的序列,除了它似乎重复的事实。

我想我错过了调用Select()SelectMany()的内容,触发范围重新评估。

代码及解释我想做什么

  • 对于所有数字,循环检索数据的方法(从数据库中分页)。
  • 最终,此数据将为空(我只想在检索数据时继续处理
  • 对于检索到的每个记录,我只想处理应该处理的记录
  • 在那些应该处理的内容中,我希望并行处理x个(根据设置)。
  • 我想等到整个序列完成后退出方法(因此最后的等待调用)。

以下代码的问题

  • 我使用我知道只有1项的数据集来运行代码。
    • 因此,第0页返回1项,第1页返回0项。
  • 我的期望是该过程对一个项目运行一次。
  • 但是,我看到第0页和第1页都被调用了两次,因此进程运行了两次。

我认为这与从0开始重新评估范围的调用有关,但我无法弄清楚它是什么。

守则

var query = Observable.Range(0, int.MaxValue)
    .Select(pageNum =>
        {
            _etlLogger.Info("Calling GetResProfIDsToProcess with pageNum of {0}", pageNum);
            return _recordsToProcessRetriever.GetResProfIDsToProcess(pageNum, _processorSettings.BatchSize);
        })
    .TakeWhile(resProfList => resProfList.Any())
    .SelectMany(records => records.Where(x=> _determiner.ShouldProcess(x)))
    .Select(resProf => Observable.Start(async () => await _schoolDataProcessor.ProcessSchoolsAsync(resProf)))
    .Merge(maxConcurrent: _processorSettings.ParallelProperties)
    .Do(async trackingRequests =>
    {
        await CreateRequests(trackingRequests.Result, createTrackingPayload);

        var numberOfAttachments = SumOfRequestType(trackingRequests.Result, TrackingRecordRequestType.AttachSchool);
        var numberOfDetachments = SumOfRequestType(trackingRequests.Result, TrackingRecordRequestType.DetachSchool);
        var numberOfAssignmentTypeUpdates = SumOfRequestType(trackingRequests.Result,
            TrackingRecordRequestType.UpdateAssignmentType);

        _etlLogger.Info("Extractor generated {0} attachments, {1} detachments, and {2} assignment type changes.",
            numberOfAttachments, numberOfDetachments, numberOfAssignmentTypeUpdates);
    });

var subscription = query.Subscribe(
trackingRequests =>
{
    //Nothing really needs to happen here. Technically we're just doing something when it's done.
},
() =>
{
    _etlLogger.Info("Finished! Woohoo!");
});

await query.Wait();

2 个答案:

答案 0 :(得分:1)

这是因为您订阅了两次序列。在query.Subscribe(...)后再次在query.Wait()

Observable.Range(0, int.MaxValue)是一个冷酷的观察者。每次订阅时,都会再次进行评估。您可以通过Publish()发布,然后订阅它,然后Connect()然后Wait()来使可观察的热点变为热点。如果在已经产生最后一个元素之后调用InvalidOperationException,则会增加获得Wait()的风险。更好的选择是LastOrDefaultAsync()

那会让你得到这样的东西:

var connectable = query.Publish();
var subscription = connectable.Subscribe(...);
subscription = new CompositeDisposable(connectable.Connect(), subscription);
await connectable.LastOrDefaultAsync();

或者您可以避免等待并直接使用ToTask()返回任务(从方法签名中删除异步)。

return connectable.LastOrDefaultAsync().ToTask();

转换为任务后,您可以使用Wait()同步等待该任务(不要将Task.Wait()Observable.Wait()混淆)。

connectable.LastOrDefaultAsync().ToTask().Wait();

然而,很可能你根本不想等!等待异步上下文毫无意义。你应该做什么把在序列完成后需要运行的剩余代码放在订阅的OnComplete()部分。如果您有(清理)代码,即使您取消订阅(Dispose)也需要运行,请考虑Observable.UsingFinally(...)方法以确保运行此代码。

答案 1 :(得分:1)

正如已经提到的,重复<a>的原因是您订阅了两次 - 一次使用Observable.Range,一次使用.Subscribe(...)

在这种情况下,我会使用一个非常简单的阻塞调用来获取值。就这样做:

.Wait()

var results = query.ToArray().Wait(); 将多值.ToArray()转换为单个值IObservable<T>IObservable<T[]>将其转换为.Wait()。这是确保只有一个订阅,阻止和获取所有值的简单方法。

在你的情况下,你可能不需要所有的价值观,但我认为这是一个很好的习惯。