【问题标题】:How do I change the Rx Builder implementation to fix the stack overflow exception?如何更改 Rx Builder 实现以修复堆栈溢出异常?
【发布时间】:2011-05-28 14:44:25
【问题描述】:

我正在尝试设计一个 Rx Builder 以在 F# 计算表达式语法中使用反应式扩展。如何修复它以使其不会炸毁堆栈?就像下面的 Seq 示例。 是否有计划将 RxBuilder 的实现作为响应式扩展的一部分或作为 .NET Framework 未来版本的一部分?

open System
open System.Linq
open System.Reactive.Linq

type rxBuilder() =    
    member this.Delay f = Observable.Defer f
    member this.Combine (xs: IObservable<_>, ys : IObservable<_>) = 
        Observable.merge xs ys      
    member this.Yield x = Observable.Return x
    member this.YieldFrom (xs:IObservable<_>) = xs

let rx = rxBuilder()

let rec f x = seq { yield x 
                    yield! f (x + 1) }

let rec g x = rx { yield x 
                    yield! g (x + 1) }


//do f 5 |> Seq.iter (printfn "%A")

do g 5 |> Observable.subscribe (printfn "%A") |> ignore

do System.Console.ReadLine() |> ignore

【问题讨论】:

  • 这可能会让你感兴趣:feedproxy.google.com/~r/FCode/~3/rDQbQHOROOw/…
  • 您是否查看过最新 Rx 版本中的 Expand 运算符?它处理某些递归场景。
  • 你会如何应用 Observable.Expand 来解决这个问题?你有任何解释 Expand 语义的链接吗?
  • @Ramon:这是 Holoed 的博客 :)

标签: f# system.reactive computation-expression


【解决方案1】:

简短的回答是,Rx Framework 不支持使用像这样的递归模式生成 observables,所以它不容易完成。用于 F# 序列的 Combine 操作需要一些可观察对象不提供的特殊处理。 Rx 框架可能希望您使用 Observable.Generate 生成 observables,然后使用 LINQ 查询/F# 计算构建器来处理它们。

无论如何,这里有一些想法 -

首先,您需要将Observable.merge 替换为Observable.Concat。第一个并行运行两个 observable,而第二个首先从第一个 observable 产生所有值,然后从第二个 observable 产生值。在此更改之后,sn-p 将在堆栈溢出之前至少打印约 800 个数字。

堆栈溢出的原因是Concat 创建了一个调用Concat 的observable 来创建另一个调用Concat 等的observable。解决此问题的一种方法是添加一些同步。如果您使用的是 Windows 窗体,则可以修改 Delay 以便它在 GUI 线程上调度 observable(丢弃当前堆栈)。这是一个草图:

type RxBuilder() =   
  member this.Delay f = 
      let sync = System.Threading.SynchronizationContext.Current 
      let res = Observable.Defer f
      { new IObservable<_> with
          member x.Subscribe(a) = 
            sync.Post( (fun _ -> res.Subscribe(a) |> ignore), null)
            // Note: This is wrong, but we cannot easily get the IDisposable here
            null }
  member this.Combine (xs, ys) = Observable.Concat(xs, ys)
  member this.Yield x = Observable.Return x
  member this.YieldFrom (xs:IObservable<_>) = xs

要正确实现这一点,您必须编写自己的Concat 方法,这非常复杂。这个想法是:

  • Concat 返回一些特殊类型,例如IConcatenatedObservable
  • 递归调用该方法时,您将创建一个相互引用的IConcatenatedObservable
  • Concat 方法将查找此链以及何时存在例如三个对象,它将丢弃中间的一个(始终保持链的长度最多为 2)。

这对于 StackOverflow 的答案来说有点太复杂了,但对于 Rx 团队来说可能是一个有用的反馈。

【讨论】:

  • 谢谢。很好的答案。我希望 Rx 团队正在倾听 :)
  • 你可以使用TakeUntil 来丢弃和处理旧的 observables 吗?当然,你需要一些方法来获取之前的 observable。
【解决方案2】:

请注意,这已在 Rx v2.0 中得到修复(正如这里已经提到的),更普遍地适用于所有排序运算符(Concat、Catch、OnErrorResumeNext)以及命令式运算符(If、While 等) .

基本上,您可以将此类运算符视为订阅终端观察者消息中的另一个序列(例如,Concat 在接收到当前的 OnCompleted 消息时订阅下一个序列),这就是尾递归类比的来源在。

在 Rx v2.0 中,所有尾递归订阅都被扁平化为类似队列的数据结构,以便一次处理一个,并与下游观察者对话。这避免了观察者为连续的序列订阅而相互交谈的无限增长。

【讨论】:

    【解决方案3】:

    此问题已在 Rx 2.0 Beta 中修复。这是test

    【讨论】:

      【解决方案4】:

      这样的事情呢?

      type rxBuilder() =    
         member this.Delay (f : unit -> 'a IObservable) = 
                     { new IObservable<_> with
                          member this.Subscribe obv = (f()).Subscribe obv }
         member this.Combine (xs:'a IObservable, ys: 'a IObservable) =
                     { new IObservable<_> with
                          member this.Subscribe obv = xs.Subscribe obv ; 
                                                      ys.Subscribe obv }
         member this.Yield x = Observable.Return x
         member this.YieldFrom xs = xs
      
      let rx = rxBuilder()
      
      let rec f x = rx { yield x 
                         yield! f (x + 1) }
      
      do f 5 |> Observable.subscribe (fun x -> Console.WriteLine x) |> ignore
      
      do System.Console.ReadLine() |> ignore
      

      http://rxbuilder.codeplex.com/(为试验 RxBuilder 而创建)

      xs 一次性用品未接线。一旦我尝试连接一次性用品,它就会重新炸毁堆栈。

      【讨论】:

        【解决方案5】:

        如果我们从这个计算表达式(又名 Monad)中删除语法糖,我们将得到:

        let rec g x = Observable.Defer (fun () -> Observable.merge(Observable.Return x, g (x + 1) )
        

        或者在 C# 中:

        public static IObservable<int> g(int x)
        {
            return Observable.Defer<int>(() =>
            {
              return Observable.Merge(Observable.Return(x), g(x + 1));                    
            });
        }
        

        这绝对不是尾递归。我认为如果您可以使其尾递归,那么它可能会解决您的问题

        【讨论】:

          猜你喜欢
          • 2010-11-27
          • 2020-11-14
          • 1970-01-01
          • 1970-01-01
          • 2012-09-12
          • 2016-03-14
          • 1970-01-01
          相关资源
          最近更新 更多