如何弱订阅events/observables

How to weakly subscribe to events/observables

我有一个类似静态的(发布者生命周期 = 应用程序生命周期)事件,我需要从视图中订阅。我无法可靠地确定视图何时导航离开(在 Xamarin.Forms NavigationPage 中按下导航栏后退按钮就是一个示例),因此我无法确定视图何时应取消订阅可观察对象。 (我知道在 OnAppearing/OnDisappearing 中 subscribe/unsubscribe 是可能的,但这有其自身的一系列问题,我不会在这里详细介绍。)

因此,我发现自己需要让视图弱订阅事件,即允许视图在不必取消订阅事件的情况下被垃圾收集。理想情况下,我想要一些可以按照 myObj.myEvent |> Observable.AsWeak |> Observable.Subscribe ...myObj.myEvent |> Observable.SubscribeWeakly ... 或简单地 myObj.myEvent.SubscribeWeakly ....

行使用的东西

不幸的是,我不知道如何实现它。我听说过 System.WeakReference class,但这对我来说都是全新的,我不知道如何正确使用它 - 我见过的大多数例子对于我正在尝试的东西来说似乎过于复杂要做,这意味着要么我想要不同的东西,要么表面下的陷阱比我怀疑的要多得多。

如何在 F# 中订阅 events/observables,同时允许订阅者在不取消订阅的情况下被垃圾回收?

相似但不重复的问题:

我已经找到了一个相对简单的功能,它似乎可以正常工作,但我真的不知道我在做什么,所以我把它放在 Code Review SE. It's based on information from Samuel Jack's Weak Events in .Net, the easy way as well as solution 4 in CodeProject's Weak Events in C#

实施

module Observable =
    open System

    // ('a -> 'b -> unit) -> 'a -> IObservable<'b>
    let subscribeWeakly callback target source = 

        let mutable sub:IDisposable = null
        let mutable disposed = false
        let wr = new WeakReference<_>(target)

        let dispose() =
            lock (sub) (fun () -> 
                if not disposed then sub.Dispose(); disposed <- true)

        let callback' x =
            let isAlive, target = wr.TryGetTarget()
            if isAlive then callback target x else dispose()

        sub <- Observable.subscribe callback' source
        sub

使用示例

请参阅下面的 WeakSubscriber 类型。

重要

您必须使用回调的 me 参数来调用相关方法。如果您在回调中使用 this,由于上述文章中所述的原因,您最终仍会得到一个强引用。出于同样的原因(我猜?),您不能在使用 let 定义的 class 中调用 "plain" 函数。 (但是,您可以将方法定义为 private。)

测试

助手 classes:

type Publisher() =
    let myEvent = new Event<_>()
    [<CLIEvent>] member this.MyEvent = myEvent.Publish
    member this.Trigger(x) = myEvent.Trigger(x)


type StrongSubscriber() =

    member this.MyMethod x = 
        printfn "Strong: method received %A" x

    member this.Subscribe(publisher:Publisher) =
        publisher.MyEvent |> Observable.subscribe this.MyMethod
        publisher.MyEvent |> Observable.subscribe 
                             (fun x -> printfn "Strong: lambda received %A" x)


type WeakSubscriber() =

    member this.MyMethod x = 
        printfn "Weak: method received %A" x

    member this.Subscribe(publisher:Publisher) =
        publisher.MyEvent |> Observable.subscribeWeakly
                             (fun (me:WeakSubscriber) x -> me.MyMethod x) this
        publisher.MyEvent |> Observable.subscribeWeakly
                             (fun _ x -> printfn "Weak: lambda received %A" x) this

实测:

[<EntryPoint>]
let main argv = 

    let pub = Publisher()

    let doGc() =
        System.GC.Collect()
        System.GC.WaitForPendingFinalizers()
        System.GC.Collect()
        printfn "\nGC completed\n"

    let someScope() =
        let strong = StrongSubscriber()
        let weak = WeakSubscriber()
        strong.Subscribe(pub)
        weak.Subscribe(pub)

        doGc() // should not remove weak subscription since it's still in scope
        printfn "All subscribers should still be triggered:"
        pub.Trigger(1)

    someScope()

    doGc() // should remove weak subscriptions
    printfn "Weak subscribers should not be triggered:"
    pub.Trigger(2)

    System.Console.ReadKey() |> ignore

    0

输出:

GC completed

All subscribers should still be triggered:
Strong: method received 1
Strong: lambda received 1
Weak: method received 1
Weak: lambda received 1

GC completed

Weak subscribers should not be triggered:
Strong: method received 2
Strong: lambda received 2