asynchronousf#disposable

How does IDisposable work with use and return?


In F# async workflows, we can define a resource that should be cleaned up with the use keyword.

But how does use interact with return?

For example, given this code:

let createResource = async {
  use r = Resource ()

  do! operationThatMightThrow r

  return r
}

async {
  use! r = createResource

  printfn "%O" r
}
|> Async.RunSynchronously

Where will the calls to Resource.Dispose happen?

How can I design this so that the r is always cleaned up (even if operationThatMightThrow throws)?


Solution

  • I usually have two solutions.

    The first solution is actively capturing the exception, manually disposing the disposable object, and re-throwing the exception:

    let createResource = async {
        let r = new Resource ()
        try do! operationThatMightThrow r
        with e -> (r :> IDisposable).Dispose(); raise e
        return r
    }
    

    The second solution is to use a continuation function that will have access to the disposable object before the async returns:

    let createResource cont = async {
        use r = new Resource ()
        do! operationThatMightThrow r
        return cont r
    }
    
    async {
        let! x = createResource (fun r -> printfn "in cont: %O" r)
        ...
    }