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)?
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)
...
}
They will occur before the value is returned from the computation expression, semantically they will happen in a finally
block. If you want to look at the source of the generated using statement, you can find it here. It effectively generates an access-controlled dispose function that calls Dispose()
on the resource you pass in, then makes an asynchronous try-finally block with that function in the finally clause.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With