首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在完成过程中MailboxProcessor崩溃

在完成过程中MailboxProcessor崩溃
EN

Stack Overflow用户
提问于 2018-02-25 17:08:53
回答 2查看 108关注 0票数 2

此代码运行在Mono上(5.4.1.7)。

在我的web应用程序中,我使用F#的代理来处理很多数据处理,其中之一就是关闭。当处理发布的关机消息时,代理会清理一些东西并停止其消息循环。这很好,但是如果我尝试从Finalize()上执行关闭操作,我的脸就会爆炸。我成功地复制了以下内容:

代码语言:javascript
复制
open System
open System.Threading

type ConsoleMessage =
    | Clear
    | Println of string
    // Reply back (with unit) so that calling code is able to wait for the agent to clean up (for code dependent on the
    // agent's resources definitely being released and such)
    | Shutdown of AsyncReplyChannel<unit>

type ConsoleAgent() =
    let mutable disposed = false
    let mutable stopped = false

    let agent = MailboxProcessor.Start(fun agent ->
        let rec messageLoop () = async {
            let! message = agent.Receive ()
            match message with
            | Clear -> System.Console.Clear ()
            | Println str -> printfn "%s" str
            | Shutdown rc ->
                // Cleanup goes here
                printfn "Shutting Down"
                stopped <- true
                rc.Reply ()
            System.Threading.Thread.Sleep 100
            if not stopped then
                return! messageLoop () }
        messageLoop ())

    member this.Post msg = agent.Post msg

    member this.PostAndAsyncReply msg = agent.PostAndAsyncReply msg

    member this.Dispose disposing =
        printfn "Disposing (disposing = %b)" disposing
        if not disposed then
            Async.RunSynchronously (agent.PostAndAsyncReply Shutdown)
            disposed <- true

    override this.Finalize () =
        this.Dispose false

    interface IDisposable with
        member this.Dispose () =
            this.Dispose true

module Main =
    [<EntryPoint>]
    let main args =
        let console = new ConsoleAgent()
        console.Post (Println "Print 1")
        console.Post (Println "Print 2")
        Thread.Sleep 1000
        0

当然,在实际应用程序中,它们与控制台打印无关。

下面是我看到的堆栈痕迹:

代码语言:javascript
复制
Unhandled Exception:
System.NullReferenceException: Object reference not set to an instance of an object
  at System.Runtime.Remoting.Contexts.SynchronizationAttribute.EnterContext () [0x00000] in /Users/builder/data/lanes/4992/mono-mac-sdk/external/bockbuild/builds/mono-x64/mcs/class/corlib/System.Runtime.Remoting.Contexts/SynchronizationAttribute.cs:184 
  at System.Threading.WaitHandle.WaitOneNative (System.Runtime.InteropServices.SafeHandle waitableSafeHandle, System.UInt32 millisecondsTimeout, System.Boolean hasThreadAffinity, System.Boolean exitContext) [0x0002d] in /Users/builder/data/lanes/4992/mono-mac-sdk/external/bockbuild/builds/mono-x64/mcs/class/corlib/System.Threading/WaitHandle.cs:111 
  at System.Threading.WaitHandle.InternalWaitOne (System.Runtime.InteropServices.SafeHandle waitableSafeHandle, System.Int64 millisecondsTimeout, System.Boolean hasThreadAffinity, System.Boolean exitContext) [0x00014] in /Users/builder/data/lanes/4992/mono-mac-sdk/external/bockbuild/builds/mono-x64/mcs/class/referencesource/mscorlib/system/threading/waithandle.cs:250 
  at System.Threading.WaitHandle.WaitOne (System.Int64 timeout, System.Boolean exitContext) [0x00000] in /Users/builder/data/lanes/4992/mono-mac-sdk/external/bockbuild/builds/mono-x64/mcs/class/referencesource/mscorlib/system/threading/waithandle.cs:239 
  at System.Threading.WaitHandle.WaitOne (System.Int32 millisecondsTimeout, System.Boolean exitContext) [0x00019] in /Users/builder/data/lanes/4992/mono-mac-sdk/external/bockbuild/builds/mono-x64/mcs/class/referencesource/mscorlib/system/threading/waithandle.cs:206 
  at Microsoft.FSharp.Control.AsyncImpl+ResultCell`1[T].TryWaitForResultSynchronously (Microsoft.FSharp.Core.FSharpOption`1[T] timeout) [0x0002a] in <5a7d678a904cf4daa74503838a677d5a>:0 
  at Microsoft.FSharp.Control.CancellationTokenOps.RunSynchronouslyInCurrentThread[a] (System.Threading.CancellationToken token, Microsoft.FSharp.Control.FSharpAsync`1[T] computation) [0x0001c] in <5a7d678a904cf4daa74503838a677d5a>:0 
  at Microsoft.FSharp.Control.CancellationTokenOps.RunSynchronously[a] (System.Threading.CancellationToken token, Microsoft.FSharp.Control.FSharpAsync`1[T] computation, Microsoft.FSharp.Core.FSharpOption`1[T] timeout) [0x00013] in <5a7d678a904cf4daa74503838a677d5a>:0 
  at Microsoft.FSharp.Control.FSharpAsync.RunSynchronously[T] (Microsoft.FSharp.Control.FSharpAsync`1[T] computation, Microsoft.FSharp.Core.FSharpOption`1[T] timeout, Microsoft.FSharp.Core.FSharpOption`1[T] cancellationToken) [0x00070] in <5a7d678a904cf4daa74503838a677d5a>:0 
  at Program+ConsoleAgent.Dispose (System.Boolean disposing) [0x00027] in /Users/jwostenberg/Code/FSharp/Sandbox/Sandbox/Program.fs:38 
  at Program+ConsoleAgent.Finalize () [0x00000] in /Users/jwostenberg/Code/FSharp/Sandbox/Sandbox/Program.fs:42 

此外,如果通过dispose模式正确地释放对象(例如将let console = new ConsoleAgent()更改为use console = new ConsoleAgent()),则不会发生这种情况。在我自己的代码中,我不可能做到这一点,因为我没有对这些代理的直接引用(很多代理一次运行),但我不应该让它们通过垃圾收集器处理吗?

这是我的错,还是F#的错误,还是Mono的错?目前,我已经将Dispose()方法的相关部分封装在一个try/catch中,它只记录异常,但这感觉很脏。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-02-25 20:22:58

“处置”方法的论点是有道理的。它区分了处理的托管应用程序和非托管应用程序。简而言之,Dispose(true)意味着这个调用是显式的(使用语句或F#的use),它基本上是“正常”.NET编程的延续。

处置(假)意味着终结正在发生。这意味着任何引用的.NET对象都可以是活动的、处理的或最终的。因此,您的代码只需要关心非托管资源,而不需要尝试以任何其他方式调用或使用托管对象。

重要的是,Dispose()不能自动调用,而finaliser则是。要使示例正确,需要进行两项更改:

  • 显式控制可支配对象的状态。
  • 仅在对象被释放时才发送消息,而不是最终确定

代码:

代码语言:javascript
复制
    member this.Dispose disposing =
        if disposing && not disposed then
            Async.RunSynchronously (agent.PostAndAsyncReply Shutdown)
            disposed <- true

module Main =
    [<EntryPoint>]
    let main args =
        use console = new ConsoleAgent()
        Thread.Sleep 1000
        0
票数 2
EN

Stack Overflow用户

发布于 2018-02-25 18:09:44

您需要覆盖Finalize的场景很少,而且看起来不适用于您的用例。参见“实现者备注”一节这里整篇文章

默认情况下,Object.Finalize方法什么也不做,但是您应该只在必要时覆盖Finalize,并且只释放非托管资源。

您的评论:

如何确保MailboxProcessor消息循环在没有Finalize的情况下被关闭?

您可以只使用IDisposable模式,或者更显式地管理MailboxProcessor的生命周期,这可能需要重构您的项目。

在我自己的代码中,我不可能做到这一点,因为我没有对这些代理的直接引用(很多代理一次运行),但我不应该让它们通过垃圾收集器处理吗?

是的,如果他们不拥有非托管资源,你应该能够让他们“自然”地处置。很难说,如果没有看到真实的用例,但听起来您需要对处理器的生命周期有更多的控制。这可能有点像一个XY问题

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48976175

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档