-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support DisposeAsync in async computation expression #866
Comments
The challenge to this is that |
Could this be prototyped outside of FSharp.Core with an Extension Method to the async builder? https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions#extending-existing-builders-with-new-custom-operations |
It would take some work to do it right, but the CE mechanism is flexible enough to support extensions here. Here's a dumb-but-I-think-correct implementation that calls IAsyncDisposable.DisposeAsync in a blocking manner (to more easily fit in with the current signatures): open System
open System.Threading.Tasks
open System.Threading
type TrackingDisposer() =
let mutable disposed = false
member _.Disposed = disposed
interface IAsyncDisposable with
member __.DisposeAsync() =
disposed <- true
ValueTask()
type AsyncBuilder with
member builder.Using(resource:#IAsyncDisposable, f: #IAsyncDisposable -> Async<'a>) =
let mutable x = 0
let disposeFunction _ =
if Interlocked.CompareExchange(&x, 1, 0) = 0 then
let t = resource.DisposeAsync().AsTask() |> Async.AwaitTask
Async.RunSynchronously t
async.TryFinally(f resource, disposeFunction)
let test () =
let d = TrackingDisposer()
async {
use _ignored = d
return ()
}
|> Async.RunSynchronously
d.Disposed if you call |
@baronfel But if I try this with my own builder then I get the error "The type 'IAsyncDisposable' is not compatible with the type 'System.IDisposable'F# Compiler(1)" when I try to UPDATE: Please ignore, I was testing inside an Expecto |
does it really require #853? You'd normally make a using like this: let tryFinally (tryPart: unit -> Task<'a>) (finallyPart: unit -> unit) = // ...
let using body disposable =
tryFinally (fun () -> body disposable) (fun () -> disposable.Dispose()) To make IAsyncDisposable work, one could theoretically use functions with signatures similar to these: let tryAsyncFinally (tryPart: unit -> Task<'a>) (finallyPart: unit -> Task<unit>) = // ...
let usingAsync body asyncDisposable =
tryAsyncFinally (fun () -> body asyncDisposable) (fun () -> asyncDisposable.DisposeAsync())
I actually once added an implementation of this in TaskBuilder.fs. hoping to be able to add an overload for the member __.Using(disposable : #IDisposable, body : #IDisposable -> Step<'u>) = ...
member __.Using(disposable : #IAsyncDisposable, body : #IAsyncDisposable -> Step<'u>) = ... |
Yes, that's what I meant 🙂
The above are actually syntactic sugar for the following: member __.Using<'t, 'u when 't :> IDisposable>(disposable: 't, body: 't -> Step<'u>) = ...
member __.Using<'t, 'u when 't :> IAsyncDisposable>(disposable: 't, body: 't -> Step<'u>) = ... and method resolution doesn't take type constraints into consideration to choose an overload. |
So basically we can't have different overloads of Using (even though overloads are supported in CE builders IIRC) and have to wait for language support just so we can use a method with a new name? That's... rather unfortunate. |
A related problem is that the recommended method for sending events to Azure Event Hubs nowadays is using |
@dsyme now that we have APIs depending on this type (and pattern, really) it seems like the right thing to put on the docket for F# vNext |
yeah that situation with eventhubs is really worrying. |
I'm marking this as approved - we should sort this out one way or another, and also document the necessary workaround |
Noting here that GRPC (which is very well supported and promoted in .net now) makes use of IAsyncDisposable so there's a growing gap here in correctly consuming these libraries (without hacks like calling |
Most natural option seems to be to support At a later moment we could always decide to add a @dsyme thoughts? |
This would need overloading on TryFInally? Which I think will always cause problems. Diving straight in and supporting a TryFinallyBind might make more sense - using it if there is any (syntactic) binding in the finally section. |
I was thinking about What I'd like to prevent is a proliferation of new methods that must all be implemented to provide a generally expected set of functionality. IAsyncDisposable/M<'T> support will be expected and ideally any try finally form can be satisfied by implementing a single method. |
what's annoying is that task {
let sender = client.Value.CreateSender topic
try
do! // stuff
finally
do! sender.DisposeAsync() // This construct may only be used within computation expressions
} |
With F# 6 task support, I am able to [<EntryPoint>]
let main argv =
let t = task {
use serviceBus = new ServiceBusClient(connString)
// other code
return 0
}
t.Result The ServiceBusClient only implements |
I just tested this a bit more extensively to make sure:
This all seems to work well now in |
I will leave this to @dsyme if this should be a considered a "no" for now. The original filing of the issue was before |
I think we should do this. Async should respect IAsyncDisposable. Marking as approved-in-principle. |
I wanted to mention I did a naive implementation over here in FsToolkit. Not sure what holes this implementation has, would love some feedback if possible. |
Support DisposeAsync in async/task computation expression
I propose we add support for the IAsyncDisposable interface in async computation.
The addition of a new keyword
use!
seems a little bit weird because it would be only for async builder. I suppose we could try to implement it in the currentUsing
method of the builderThe existing way of approaching this problem in F# is calling the DisposeAsync manually and handling the error case by hands.
Pros and Cons
The advantages of making this adjustment to F# are ...
If IAsyncDisposable interface is available we should use it instead of the synchronous one.
The usage of StreamWriter on AspNet Core (3.x) reponse body with the keyword
use
will raise exception due to synchronous flushThe disadvantages of making this adjustment to F# are ..
Don't see any
Extra information
Estimated cost (XS, S, M, L, XL, XXL): S
Related suggestions: (put links to related suggestions here)
Affidavit (please submit!)
Please tick this by placing a cross in the box:
Please tick all that apply:
The text was updated successfully, but these errors were encountered: