如何弱订阅事件/可观察量



我有一个类似静态(发布者生存期 = 应用程序生存期(的事件,我需要从视图中订阅。我无法可靠地确定视图何时导航离开(例如,在 Xamarin.Forms 导航页中按下导航栏后退按钮(,因此我无法确定视图何时应取消订阅可观察量。(我知道可以在OnAppearing/OnDisappearing中订阅/取消订阅,但这会带来一系列问题,我不会在这里详细介绍。

因此,我发现自己需要让视图弱订阅事件,即允许对视图进行垃圾回收,而不必取消订阅事件。理想情况下,我想要一些可以沿着myObj.myEvent |> Observable.AsWeak |> Observable.Subscribe ...,或myObj.myEvent |> Observable.SubscribeWeakly ...,或简单地myObj.myEvent.SubscribeWeakly ...使用的东西。

不幸的是,我不知道如何实现这一点。我听说过 System.WeakReference 类,但这对我来说都是非常新的,我不知道如何正确使用它 - 我看到的大多数示例对于我想要做的事情来说似乎都过于复杂,这意味着要么我想要一些不同的东西,要么表面之下的陷阱比我怀疑的要多得多。

如何在 F# 中订阅事件/可观察量,同时允许在不取消订阅的情况下对订阅者进行垃圾回收?

类似但不重复的问题:

  • F# 可观察事件是避免、调解还是与弱引用的需求无关?该问题询问 F# 中是否需要弱引用,而不是如何实现上述功能。在这方面,这个问题的唯一答案也没有帮助。

我已经到达了一个相对简单的函数,它似乎可以正常工作,尽管我真的不知道我在做什么,所以我把它放在代码审查SE上。它基于 Samuel Jack 的 .Net 中的弱事件、简单的方法以及 CodeProject 的 C# 弱事件中的解决方案 4 中的信息。

实现

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定义的类中调用"普通"函数。(但是,您可以将该方法定义为private

测试

帮助程序类:

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 completedn"
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

最新更新