Rx - 无功扩展 - 从第一个Observable到第二个Objectable的条件切换

时间:2015-12-01 21:29:29

标签: c# system.reactive reactive-programming

我有2个数据源:在线和离线(缓存)。它们都返回包含2个标志的对象的IObservable - IsSuccess和IsCached。我想从在线源获取数据,但仅在IsSuccess = true时。如果失败,我想从离线源获取数据。另外,我希望将新数据保存在缓存中以备将来使用。我不确定如何在RX中做到最好。 这是我的实现,但我认为它可以做得更好

public IObservable<Result<SampleModel>> GetSampleModel()
    {
        IObservable<Result<SampleModel>> onlineObservable = _onlineSource.GetData<SampleModel>();
        IObservable<Result<SampleModel>> offlineObservable = _offlineSource.GetData<SampleModel>();

        var subject = new Subject<Result<SampleModel>>();

        onlineObservable.Do(async (result) =>
        {
            if (result.IsSuccess)
            {
                await _offlineSource.CacheData(result.Data).ConfigureAwait(false);
            }
        }).Subscribe((result) =>
        {
            if (result.IsSuccess)
            {
                subject.OnNext(result);
            }
            subject.OnCompleted();
        });

        return subject.Concat(offlineObservable).Take(1);
    }

结果类 - 数据包装器:

public class Result<T>
{
    public Result(Exception exception)
    {
        Exception = exception;
    }

    public Result(T data, bool isCached = false)
    {
        IsCached = isCached;
        IsSuccess = true;
        Data = data;
    }

    public bool IsSuccess { get; private set; }
    public bool IsCached { get; private set; }
    public T Data { get; private set; }
    public Exception Exception { get; private set; }
}

1 个答案:

答案 0 :(得分:1)

您的实施将无法可靠地运作,因为那里存在竞争条件。考虑一下:

var temp = GetSampleModel(); // #1
// Do some long operation here
temp.Subscribe(p => Console.WriteLine(p)); // #2

在这种情况下,获取数据将从#1开始,如果在#2执行之前接收到数据并将其推送到主题,则无论您等待多长时间都不会打印任何内容。

通常,您应该避免在返回IObservable的函数内进行订阅以避免此类问题。使用Do也是一种难闻的气味。您可以使用ReplaySubjectAsyncSubject修复代码,但在这种情况下,我通常更喜欢Observable.Create。这是我的重写:

public IObservable<SampleModel> GetSampleModel(IScheduler scheduler = null)
{
    scheduler = scheduler ?? TaskPoolScheduler.Default;
    return Observable.Create<SampleModel>(observer =>
    {
        return scheduler.ScheduleAsync(async (s, ct) =>
        {
            var onlineResult = await _onlineSource.GetData<SampleModel>().FirstAsync();
            if (onlineResult.IsSuccess)
            {
                observer.OnNext(onlineResult.Data);
                await _offlineSource.CacheData(onlineResult.Data);
                observer.OnCompleted();
            }
            else
            {
                var offlineResult = await _offlineSource.GetData<SampleModel>().FirstAsync();
                if (offlineResult.IsSuccess)
                {
                    observer.OnNext(offlineResult.Data);
                    observer.OnCompleted();
                }
                else
                {
                    observer.OnError(new Exception("Could not receive model"));
                }
            }
            return Disposable.Empty;
        });
    });
}

你可以看到它仍然不是很漂亮。我认为这是因为您选择不使用处理错误的自然Rx系统,而是将您的值包装在Result类型中。如果将存储库方法更改为handle errors in Rx way,则生成的代码更简洁。 (请注意,我已将您的Result类型更改为MaybeCached,并且我认为现在两个来源都返回IObservable<SampleModel>,这是cold observable返回单个结果或错误) :

public class MaybeCached<T>
{
    public MaybeCached(T data, bool isCached)
    {
        IsCached = isCached;
        IsSuccess = true;
    }

    public bool IsCached { get; private set; }
    public T Data { get; private set; }
}

public IObservable<SampleModel> GetSampleModel()
{
    _onlineSource
        .GetData<SampleModel>()
        .Select(d => new MaybeCached(d, false))
        .Catch(_offlineSource
                    .GetData<SampleModel>()
                    .Select(d => new MaybeCached(d, true))
        .SelectMany(data => data.IsCached ? Observable.Return(data.Data) : _offlineSource.CacheData(data.Data).Select(_ => data.Data));
}
此处使用

Catch来获取您要求的条件开关。