简体   繁体   English

在完成期间MailboxProcessor崩溃

[英]MailboxProcessor crashes during Finalize

This code is running on Mono (5.4.1.7). 该代码在Mono(5.4.1.7)上运行。

I'm using F#'s Agents to handle a lot of data processing in my web application, and one of these messages is Shutdown. 我正在使用F#的代理来处理Web应用程序中的许多数据处理,其中之一就是关机。 When a posted Shutdown message is processed, the agent cleans up some things and stops its message loop. 处理已发布的关机消息后,代理会清理某些内容并停止其消息循环。 This works fine and dandy, but blows up in my face if I try to perform a Shutdown from Finalize() . 这很好用,但如果我尝试从Finalize()执行Shutdown,它会炸毁我的脸。 I've managed to reproduce this: 我设法重现了这一点:

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

Of course in the real application they have nothing to do with console printing. 当然,在实际的应用程序中,它们与控制台打印无关。

Here's the stacktrace I get: 这是我得到的堆栈跟踪:

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 

What's more, this doesn't happen if the object is disposed properly via the dispose pattern (eg change let console = new ConsoleAgent() to use console = new ConsoleAgent() ). 而且,如果通过处置模式正确处置了对象,则不会发生这种情况(例如,将let console = new ConsoleAgent()更改为use console = new ConsoleAgent() )。 I can't really do this in my own code without bending over backwards because I don't have direct references to these agents (there are many of them running at a time), but shouldn't I be able to let them dispose via the garbage collector anyways? 我不能在不弯腰的情况下用自己的代码真正做到这一点,因为我没有对这些代理的直接引用(一次运行着许多代理),但是我不应该让它们通过反正垃圾收集器?

Is this my fault, F#'s fault, or Mono's fault? 这是我的错,F#的错还是Mono的错? For now, I've wrapped the relevant part of the Dispose() method in a try/catch that just logs the exception, but this feels really dirty. 现在,我将Dispose()方法的相关部分包装在一个try / catch中,该日志仅记录异常,但这确实很脏。

There are very few scenarios where you need to override Finalize , and it doesn't look like your use case applies. 在极少数情况下,您需要覆盖Finalize ,并且看起来不适合您的用例。 See "Notes for implementers" section here and this entire article . 请参阅此处的 “实施者注意事项”部分以及整篇文章

The Object.Finalize method does nothing by default, but you should override Finalize only if necessary, and only to release unmanaged resources. 默认情况下, Object.Finalize方法不执行任何操作,但是只有在必要时才应覆盖Finalize,并且仅释放非托管资源。

Re: your comment: 回复:您的评论:

How do you ensure that the MailboxProcessor message loop is shut down without Finalize ? 如何确保在没有Finalize情况下关闭MailboxProcessor消息循环?

You could just use the IDisposable pattern, or manage the lifetimes of your MailboxProcessor s more explicitly which may require refactoring your project. 您可以只使用IDisposable模式,或者更明确地管理MailboxProcessor的生存期,这可能需要重构项目。

I can't really do this in my own code without bending over backwards because I don't have direct references to these agents (there are many of them running at a time), but shouldn't I be able to let them dispose via the garbage collector anyways? 我不能在不弯腰的情况下用自己的代码真正做到这一点,因为我没有对这些代理的直接引用(一次运行着许多代理),但是我不应该让它们通过反正垃圾收集器?

Yes, you should be able to let them dispose "naturally" assuming they don't own unmanaged resources. 是的,假设他们不拥有非托管资源,您应该能够让他们“自然”处置。 It's hard to say without seeing the real-world use case, but it sounds like you want more control over the lifetimes of the processors. 看不到实际的用例很难说,但是听起来您想要对处理器的生命周期进行更多控制。 This might be somewhat of a XY problem . 这可能是XY问题

The argument "disposing" of the method Dispose is here for reason. 出于某种原因,此处使用Dispose方法的参数“ dispose”。 It differentiates between managed and unmanaged applications of Dispose. 它区分Dispose的托管应用程序和非托管应用程序。 In brief, Dispose(true) means that this call is explicit (using statement or F#'s use ). 简而言之,Dispose(true)表示此调用是显式的(使用语句或F#的use )。 It is basically continuation of "normal" .NET programming. 它基本上是“正常” .NET编程的延续。

Dispose(false) means that finalisation is happening. Dispose(false)表示正在完成。 This means that any referenced .NET objects can be either alive, disposed, or finalised. 这意味着任何引用的.NET对象都可以处于活动状态,被处置或最终确定。 So your code is need to care about unmanaged resources only and do not try to call or use in any other way managed objects. 因此,您的代码仅需要关心非托管资源,而不要尝试调用或以任何其他方式使用托管对象。

It is important, that Dispose() is not called automatically, while finaliser is. 重要的是,终结器不会自动调用Dispose()。 Making the example correct requires two changes: 使示例正确需要进行两项更改:

  • explicitly control state of the disposable object 明确控制一次性物品的状态
  • send message only when the object is disposed, not finalised 仅在对象被处置时才发送消息,而不是最终确定

Code: 码:

    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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM