使用反应性主题

时间:2019-05-30 08:44:53

标签: c# system.reactive dynamic-data

作为警告,我是Rx的新手(2周),并且一直在尝试使用Rx,RxUI和Roland Pheasant的DynamicData

我有一个服务,该服务最初从本地持久性加载数据,然后在某些用户(或系统)指令将与服务器(示例中为TriggerServer)联系以获取其他数据或替换数据。我想出的解决方案使用一个主题,并且我遇到了许多讨论使用它们的利弊的站点。尽管我了解热/冷的基本知识,但这全都是基于阅读而非真实世界。

因此,使用以下内容作为简化版本,是解决此问题的“正确”方法,还是某些地方我没有正确理解的东西?

注意:我不确定它的重要性,但实际代码来自使用RxUI的Xamarin.Forms应用,用户输入是ReactiveCommand。

示例:

using DynamicData;
using System;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;

public class MyService : IDisposable
{

    private CompositeDisposable _cleanup;
    private Subject<Unit> _serverSubject = new Subject<Unit>();

    public MyService()
    {

        var data = Initialise().Publish();
        AllData = data.AsObservableCache();


        _cleanup = new CompositeDisposable(AllData, data.Connect());
    }

    public IObservableCache<MyData, Guid> AllData { get; }

    public void TriggerServer()
    {
        // This is what I'm not sure about...
        _serverSubject.OnNext(Unit.Default);
    }

    private IObservable<IChangeSet<MyData, Guid>> Initialise()
    {
        return ObservableChangeSet.Create<MyData, Guid>(async cache =>
        {
            // inital load - is this okay?
            cache.AddOrUpdate(await LoadLocalData());


            // is this a valid way of doing this?
            var sync = _serverSubject.Select(_ => GetDataFromServer())
                .Subscribe(async task =>
                {
                    var data = await task.ConfigureAwait(false);
                    cache.AddOrUpdate(data);
                });

            return new CompositeDisposable(sync);
        }, d=> d.Id);
    }

    private IObservable<MyData> LoadLocalData()
    {
        return Observable.Timer(TimeSpan.FromSeconds(3)).Select(_ => new MyData("localdata"));
    }

    private async Task<MyData> GetDataFromServer()
    {
        await Task.Delay(2000).ConfigureAwait(true);
        return new MyData("serverdata");
    }

    public void Dispose()
    {
        _cleanup?.Dispose();
    }
}

public class MyData
{
    public MyData(string value)
    {
        Value = value;
    }

    public Guid Id { get; } = Guid.NewGuid();

    public string Value { get; set; }
}

要运行的简单控制台应用程序:

public static class TestProgram
{
    public static void Main()
    {
        var service = new MyService();

        service.AllData.Connect()
            .Bind(out var myData)
            .Subscribe(_=> Console.WriteLine("data in"), ()=> Console.WriteLine("COMPLETE"));

        while (Continue())
        {
            Console.WriteLine("");
            Console.WriteLine("");
            Console.WriteLine($"Triggering Server Call, current data is: {string.Join(", ", myData.Select(x=> x.Value))}");
            service.TriggerServer();
        }
    }

    private static bool Continue()
    {
        Console.WriteLine("Press any key to call server, x to exit");
        var key = Console.ReadKey();
        return key.Key != ConsoleKey.X;
    }
}

2 个答案:

答案 0 :(得分:0)

第一次尝试使用Rx看起来非常好

我建议进行一些更改:

1)从构造函数中删除Initialize()调用,并将其设为公共方法-对单元测试有很大帮助,现在您可以await,如果需要的话

 public static void Main()
    {
        var service = new MyService();
        service.Initialize();

2)将Throttle添加到触发器中-这可修复对服务器的并行调用,返回相同的结果

3)不要做任何可能丢进Subscribe的事情,而应使用Do

var sync = _serverSubject
                .Throttle(Timespan.FromSeconds(0.5), RxApp.TaskPoolScheduler) // you can pass a scheduler via arguments, or use TestScheduler in unit tests to make time pass faster
                .Do(async _ =>
                {
                    var data = await GetDataFromServer().ConfigureAwait(false); // I just think this is more readable, your way was also correct
                    cache.AddOrUpdate(data);
                })
               // .Retry(); // or anything alese to handle failures
                .Subscribe();

答案 1 :(得分:0)

我将提出的建议作为解决方案,以防万一其他人在互联网上徘徊时发现了这个问题。

我最终将所有Subject一起删除,并将多个SourceCache链接在一起,所以当一个SourceCache更改时,它被推入另一个SourceCache中,依此类推。为了简洁起见,我删除了一些代码:

public class MyService : IDisposable
{
    private SourceCache<MyData, Guid> _localCache = new SourceCache<MyData, Guid>(x=> x.Id);
    private SourceCache<MyData, Guid> _serverCache = new SourceCache<MyData, Guid>(x=> x.Id);

    public MyService()
    {
        var localdata = _localCache.Connect();
        var serverdata = _serverCache.Connect();
        var alldata = localdata.Merge(serverdata);

        AllData = alldata.AsObservableCache();
    }

    public IObservableCache<MyData, Guid> AllData { get; }

    public IObservable<Unit> TriggerLocal()
    {
        return LoadLocalAsync().ToObservable();
    }

    public IObservable<Unit> TriggerServer()
    {
        return LoadServerAsync().ToObservable();
    }
}

编辑:我再次更改了此设置以删除所有缓存链-我只在内部管理一个缓存。课程不宜过早发布。