问题描述
由于observables通常是 IDisposable
所以如何改变,如果有的话,需要在事件处理程序中使用弱引用,或者任何其他基于事件的内存泄漏/ GC锁定引用?
尽管我主要关心/需要WPF,但我正在寻找更广泛的示例并试图了解可能需要弱引用的地方。 b
$ b
F#的 Observable.add
没有提供解除事件的方法,所以我认为它不太可能是源的泄漏。
示例代码:
type Notifier()=
let propChanged = new Event< _,_> ()
成员__。Foo()=()
接口INotifyPropertyChanged with
[< CLIEvent>]
成员__。PropertyChanged = propChanged.Publish
抽象成员RaisePropertyChanged:string - > unit
default x.RaisePropertyChanged(propertyName:string)= propChanged.Trigger(x,PropertyChangedEventArgs(propertyName))
Notifier():?> INotifyPropertyChanged
|> Observable.add(fun _ - > printfn我迷上了你)
实际上是相反的。 Observable.add
,由文档永久订阅该事件,并强制泄漏。
通常,使用 Observable
(在F#和C#)时,您应该倾向于使用 .subscribe
,并在完成后处理订阅处理。
正如@rmunn提到的那样,可以作为在某些情况下使用observables的替代方法(并且很好地与他们根据需要)。在编写它时,我的主要目标之一是使订阅不会泄漏 - 所有订阅都使用基于弱引用的混合推/拉模型,这样可以避免许多问题在事件和基于观察的代码中泄漏。
为了演示,我使用observables和Gjallarhorn的信号将代码的变体放在一起。如果您在发布版本中运行此版本,则在调试器外部,您会看到区别:
type Notifier()=
let propChanged = new Event< _,_>()
成员__。Foo()=()
接口INotifyPropertyChanged with
[< CLIEvent>]
成员__。PropertyChanged = propChanged.Publish
抽象成员RaisePropertyChanged:string - >单元
默认x.RaisePropertyChanged(propertyName:string)= propChanged.Trigger(x,PropertyChangedEventArgs(propertyName))
let obs()=
使用mre = new ManualResetEvent(false )
let not = Notifier()
do
let inpc = not:> INotifyPropertyChanged
inpc.PropertyChanged
|> Observable.add(fun p - > printfnHit%s!p.PropertyName)
async {
for [0 .. 10] do
do! Async.Sleep 100
printfn引发
not.RaisePropertyChanged< | sprintf%di
mre.Set()|>忽略
} |> Async.Start
printfn退出块
GC.Collect()//强制集合,清理
mre.WaitOne()|> ;忽略
让signals()=
使用mre = new ManualResetEvent(false)
let not = Mutable.create 0
do
不是
|> Signal.Subscription.create(fun v - > printfnHit%d!v)
|>忽略//扔掉订阅处理
async {
for [0 .. 10] do
do! Async.Sleep 100
printfnSetting
not.Value< - i
mre.Set()|>忽略
} |> Async.Start
printfn退出块
GC.Collect()//强制集合,清理
mre.WaitOne()|> ;忽略
[< STAThread>]
[< EntryPoint>]
let main _ =
printfnUsing observable
obs()
printfn使用信号
信号()
1
请注意,两者都做类似的事情 - 它们创建一个源代码,然后在一个单独的作用域中订阅它并丢弃一次性订阅处理程序( ) Observable.add
不过是 subscribe |> ignore
- 查看代码以获取详细信息。在调试器外部的发行版本中运行时(调试器会阻止清理发生),您会看到:
使用observable
退出块
提高
命中0!
提高
命中1!
提高
命中2!
提高
命中3!
提高
打4!
提高
打5!
提高
打6!
提高
命中7!
提高
打8!
提高
命中9!
提高
打10!
使用信号
退出块
设定
设定
设定
设定
设定
设定
设定
设定
设定
设定
设定
按任意键继续。 。 。
在可观察的情况下,调用 .add
永久持有对通知程序的引用,防止垃圾收集。对于信号,信号订阅将GC,并自动解除绑定,从而防止来自Hit的呼叫被显示。
Since observables are typically IDisposable
how does that change, if at all, the need to use weak references in event handlers, or any other event based memory leak/GC locked referencing?
While my primary concern/need is for WPF I'm looking for the broader example and trying to understand where I may need weak references.
F#'s Observable.add
doesn't provide a way to unhook the event, so I'm thinking it's less likely to be a source of leaks.Sample code:
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")
It's actually the opposite. Observable.add
, by the docs, permanently subscribes to the event, and forces a "leak". It's effectively doing an event handler addition that has no way to unsubscribe.
In general, with Observable
(in F# and C#), you should favor using .subscribe
, and disposing of the subscription handle when you're done.
As @rmunn mentioned, Gjallarhorn can serve as an alternative to using observables in some scenarios (and integrates nicely with them as needed). While writing it, one of my main goals was to make it so that subscriptions don't leak - all of the subscriptions use a hybrid push/pull model based on weak references, which prevents many of the problems with leaking in event and observable based code.
To demonstrate, I've thrown together a variation on your code, using both observables and Gjallarhorn's signals. If you run this in a release build, outside of the debugger, you'll see the difference:
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
Note that both do something similar - they create a "source", then, in a separate scope, subscribe to it and throw away the disposable subscription handle (Observable.add
is nothing but subscribe |> ignore
- see code for details.). When running in a release build outside of the debugger (the debugger prevents cleanup from happening), you see:
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 . . .
In the observable case, the call to .add
permanently holds a reference to the notifier, preventing it from being garbage collected. With signals, the signal subscription will GC, and "unhook" automatically, preventing the calls from Hit from ever being displayed.
这篇关于F#可观察事件是否消除,调解或与弱引用的需求无关?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!