0

我了解反应式编程在异步环境(Web 请求,或繁重的 IO/多线程/后台任务)中大放异彩。但是,在同步世界中,我发现反应式编程仍然给程序员释放依赖管理负担带来了很大的好处。我我正在使用 C# 编写一个类似于电子表格的桌面应用程序:大量输入,对这些输入和输出进行计算。我正在使用 RX.net 并享受它给我带来的好处免费依赖管理:当输入更改时,我不需要知道哪些计算需要redo,哪些ui需要更新。但是,由于涉及的同步/顺序计算更多,使用observable对性能的影响更大。考虑以下两种编码方式:

  private static void async_world()
  {
     Subject<string> a_ob = new Subject<string>();
     IObservable<string> A_ob = a_ob.Select(str =>
     {
        return my_to_upper(str);
     });
     IObservable<string> AA_ob = A_ob.Select(str => $"{str}{str}");
     IObservable<string> AAA_ob = A_ob.Select(str => $"{str}{str}{str}");
     IObservable<string> AA_AAA_ob = Observable.CombineLatest(AA_ob, AAA_ob,
     (AA, AAA) =>
     {
        return $"{AA}_{AAA}";
     });

     AA_AAA_ob.Subscribe(str => Console.Out.WriteLine(str));
     a_ob.OnNext("a");
  }

  private static void sync_world()
  {
     Subject<string> a_ob = new Subject<string>();

     IObservable<string> result_ob = a_ob.Select(str =>
     {
        var upper = my_to_upper(str);

        var AA = $"{upper}{upper}";
        var AAA = $"{upper}{upper}{upper}";

        return $"{AA}_{AAA}";
     });

     result_ob.Subscribe(str => Console.Out.WriteLine(str));
     a_ob.OnNext("a");
  }

假设 my_to_upper() 是一个缓慢的过程:

  private static string my_to_upper(string str)
  {
     Console.Out.WriteLine($"{str}.ToUpper...");
     for (int i = 0; i < 1000000; i++)
     {
        for (int j = 0; j < 2000; j++)
        {

        }
     }
     Console.Out.WriteLine($"{str}.ToUpper...done");
     return str.ToUpper();
  }

对于 async_world(),与 sync_world() 相比,my_to_upper() 执行了两次。当数据到达时(在每个 onNext 调用上)A_ob 执行计算并“缓存” my_to_upper() 的结果并将其传递给 AA_ob 和 AAA_ob

所以我的问题是:这是我们必须做出的权衡:让计算机以低效的性能为我们自动管理依赖关系,或者手动管理依赖关系以获得更好的性能。

4

1 回答 1

0

Publish可以通过各种重载“缓存”结果:

这是使用 Publish + RefCount 的 Published 形式的异步:

private static void async_world_publish_refcount()
{
    Subject<string> a_ob = new Subject<string>();
    IObservable<string> A_ob = a_ob
        .Select(str => my_to_upper(str)) //same function call as async_world_original, just removed braces.
        .Publish()
        .RefCount();

    IObservable<string> AA_ob = A_ob.Select(str => $"{str}{str}");
    IObservable<string> AAA_ob = A_ob.Select(str => $"{str}{str}{str}");
    IObservable<string> AA_AAA_ob = Observable.CombineLatest(AA_ob, AAA_ob,
    (AA, AAA) =>
    {
        return $"{AA}_{AAA}";
    });

    AA_AAA_ob.Subscribe(str => Console.Out.WriteLine(str));
    a_ob.OnNext("a");
}

这是一个没有 RefCount 的已发布表单:

private static void async_world_publish_only()
{
    Subject<string> a_ob = new Subject<string>();
    IObservable<string> AA_AAA_ob = a_ob
        .Select(str => my_to_upper(str)) //same function call as async_world_original, just removed braces.
        .Publish(A_ob => 
            Observable.CombineLatest(
                A_ob.Select(str => $"{str}{str}"),
                A_ob.Select(str => $"{str}{str}{str}"),
                (AA, AAA) => $"{AA}_{AAA}"
            )
        );

    AA_AAA_ob.Subscribe(str => Console.Out.WriteLine(str));
    a_ob.OnNext("a");
}

如果您喜欢功能性、反应性的事物,那么不带 RefCount 的发布形式可能是首选:它有效地缓存 observable 的结果值,并使它们在有限的 lambda 范围内可用,您必须生成一个选择器来决定要做什么做它。如有必要,您可以嵌套Publishlambda 以访问多个“缓存”的可观察值。

Publish+ RefCount,相比之下,促进了声明-y,迭代式管道。一般不推荐。

您可以在此处阅读更多关于PublishRefCount与冷可观察的信息。

于 2018-01-22T22:08:32.480 回答