【问题标题】:MailboxProcessor.Dispose does not make object GC collectibleMailboxProcessor.Dispose 不会使对象 GC 可收集
【发布时间】:2013-07-23 06:13:57
【问题描述】:

我一直在为使用 MailboxProcessor 的 F# 项目修复我的 TFS 测试运行。问题是我从 TFS 测试运行器收到的以下警告:

System.AppDomainUnloadedException:试图访问已卸载的 AppDomain。如果测试启动了一个线程但没有停止它,就会发生这种情况。确保测试启动的所有线程在完成之前都已停止。

我猜这个问题是由 MailboxProcessor 引起的。下面的 sn-p 演示了这个问题(我从 fsi 运行它):

open System.Threading
open System

type TestDisposable () =
    let cts = new CancellationTokenSource ()
    let processMessage (inbox:MailboxProcessor<int>) =
        let rec loop n =
            async {
                let! msg = inbox.Receive ()
                return! loop (n+msg)
            }
        loop 0

    let agent = MailboxProcessor<int>.Start (processMessage, cts.Token)

    interface IDisposable with
        member this.Dispose () =
            (agent :> IDisposable).Dispose ()
            cts.Cancel ()
            cts.Dispose ()
            printfn "TestDisposable.Dispose called"

do
    let weakTarget = 
        use target = new TestDisposable ()
        new WeakReference (target)

    GC.Collect()
    GC.WaitForPendingFinalizers()
    GC.WaitForFullGCComplete() |> ignore
    GC.Collect()

    printfn "WeakTarget is alive: %b" weakTarget.IsAlive

我希望输出行显示weakTarget 已死。但它活着。 我认为这表明存在一些内存泄漏。问题是我做错了什么? 而第二个问题是GC问题是否与TFS test runner问题有关。

【问题讨论】:

  • 我会说最初的错误可能是由于邮箱处理器可能会启动额外的线程,这些线程在邮箱处理器被释放后仍然存在。这是一种完全自然的性能优化。
  • 同意。问题是“我如何杀死邮箱处理器的所有优点”?
  • 我运行了一个创建新TestDisposable的无限循环,对垃圾回收是否真的发生了简单的测试。经过 500k 次迭代后,我发现内存使用量没有增加。我建议你的 GC 函数没有按照你的想法做。
  • 我再次同意。虽然,我在 C# 中成功地使用了这个构造(假设构建的单元测试项目没有优化)来测试内存泄漏,它源于被遗忘的事件处理程序,应该被取消注册。所以,这里的 F# 和 C# 之间存在一些差异。

标签: asynchronous memory-leaks f# idisposable mailboxprocessor


【解决方案1】:

您发布的示例代码将保留对 target 的引用,可能是因为您有一个顶级绑定 (use target = new TestDisposable())。

如果您将代码更改为类似下面的代码,您将看到 weakTarget 已失效,因为对 target 的引用仅在 test() 函数的本地。

do
    let test() =
        use target = new TestDisposable()
        new WeakReference(target)

    let weakTarget = test()    

    GC.Collect()
    GC.WaitForPendingFinalizers()
    GC.WaitForFullGCComplete() |> ignore
    GC.Collect()

    printfn "WeakTarget is alive: %b" weakTarget.IsA

我不知道这是否能解决您最初的问题,因为这与您编写示例代码的方式相当。

【讨论】:

  • 是的,非常感谢,这有助于解决这两个问题。我曾经在 TestClass 的默认 ctor 中的 let 绑定中创建一个测试目标,这导致了 AppDomainUnloadedException 的问题。现在我已将目标初始化移动到每个 TestMethods 主体,并且可以。虽然我真的不明白为什么在我的原始示例中,即使在目标到达其范围的末尾之后,对目标的引用仍然存在。
  • 也许是因为速记语法(完整的语法是“let x =... in ...”)隐藏了这样一个事实,即 let 后面的主体的其余部分是一个将 let 语句作为一个关闭?
猜你喜欢
  • 1970-01-01
  • 2012-01-31
  • 1970-01-01
  • 2012-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多