F#可观察事件是否消除,调解或与弱引用的需要无关?

Mas*_*low 6 f# garbage-collection memory-leaks

由于可观察性通常IDisposable如何改变,如果有的话,需要在事件处理程序中使用弱引用,或者任何其他基于事件的内存泄漏/ GC锁定引用?

虽然我主要关心/需要的是WPF,但我正在寻找更广泛的例子,并试图了解我可能需要弱引用的地方.

F#Observable.add没有提供解开事件的方法,因此我认为它不太可能成为泄密源.示例代码:

type Notifier() = 
    let propChanged = new Event<_,_>()
    member __.Foo() = ()
    interface INotifyPropertyChanged with
        [<CLIEvent>]
        member __.PropertyChanged = propChanged.Publish
    abstract member RaisePropertyChanged : string -> unit
    default x.RaisePropertyChanged(propertyName : string) = propChanged.Trigger(x, PropertyChangedEventArgs(propertyName))


Notifier() :?> INotifyPropertyChanged
|> Observable.add(fun _ -> printfn "I'm hooked on you")
Run Code Online (Sandbox Code Playgroud)

Ree*_*sey 8

F#的Observable.add没有提供解开事件的方法,所以我认为它不太可能成为漏洞的来源

实际上恰恰相反. Observable.add,由文档,永久订阅该事件,并强制"泄漏".它实际上是一个无法取消订阅的事件处理程序添加.

通常,使用Observable(在F#和C#中),您应该支持.subscribe在完成后使用和处理订阅句柄.

正如@rmunn所提到的,Gjallarhorn可以作为在某些场景中使用observable的替代方法(并根据需要与它们很好地集成).在编写它时,我的主要目标之一是使订阅不会泄漏 - 所有订阅都使用基于弱引用的混合推/拉模型,这可以防止在事件和基于可观察的事件中泄漏的许多问题码.

为了演示,我使用了observable和Gjallarhorn的信号,将代码的变体放在一起.如果您在调试器之外的发布版本中运行它,您将看到区别:

type Notifier() = 
    let propChanged = new Event<_,_>()
    member __.Foo() = ()
    interface INotifyPropertyChanged with
        [<CLIEvent>]
        member __.PropertyChanged = propChanged.Publish
    abstract member RaisePropertyChanged : string -> unit
    default x.RaisePropertyChanged(propertyName : string) = propChanged.Trigger(x, PropertyChangedEventArgs(propertyName))

let obs () =
    use mre = new ManualResetEvent(false)

    let not = Notifier()

    do       
       let inpc = not :> INotifyPropertyChanged
       inpc.PropertyChanged 
       |> Observable.add (fun p -> printfn "Hit %s!" p.PropertyName)       

       async {
            for i in [0 .. 10] do
                do! Async.Sleep 100
                printfn "Raising"
                not.RaisePropertyChanged <| sprintf "%d" i
            mre.Set () |> ignore
       } |> Async.Start

       printfn "Exiting block"

    GC.Collect() // Force a collection, to "cleanup"
    mre.WaitOne() |> ignore

let signals () =
    use mre = new ManualResetEvent(false)

    let not = Mutable.create 0

    do
       not 
       |> Signal.Subscription.create (fun v -> printfn "Hit %d!" v)
       |> ignore // throw away subscription handle

       async {
            for i in [0 .. 10] do
                do! Async.Sleep 100 
                printfn "Setting"
                not.Value <- i                
            mre.Set () |> ignore
       } |> Async.Start

       printfn "Exiting block"

    GC.Collect() // Force a collection, to "cleanup"
    mre.WaitOne() |> ignore


[<STAThread>]
[<EntryPoint>]
let main _ =
    printfn "Using observable"
    obs ()

    printfn "Using signals"
    signals ()

    1
Run Code Online (Sandbox Code Playgroud)

请注意,两者都做类似的事情 - 它们创建一个"源",然后,在一个单独的范围内,订阅它并丢弃一次性订阅句柄(Observable.add只是subscribe |> ignore- 请参阅代码以获取详细信息.).在调试器外部的发布版本中运行时(调试器阻止清理发生),您会看到:

Using observable
Exiting block
Raising
Hit 0!
Raising
Hit 1!
Raising
Hit 2!
Raising
Hit 3!
Raising
Hit 4!
Raising
Hit 5!
Raising
Hit 6!
Raising
Hit 7!
Raising
Hit 8!
Raising
Hit 9!
Raising
Hit 10!
Using signals
Exiting block
Setting
Setting
Setting
Setting
Setting
Setting
Setting
Setting
Setting
Setting
Setting
Press any key to continue . . .
Run Code Online (Sandbox Code Playgroud)

在可观察的情况下,调用.add永久保存对通知程序的引用,防止它被垃圾回收.对于信号,信号订阅将GC,并自动"解钩",防止来自Hit的呼叫被显示.

  • @Maslow它会使通知程序保持活动状态,这反过来会保留它所引用的任何内容.它充当GC的路径,以防止在持有订阅的对象消失之前收集通知程序, (2认同)