Skip to content

Commit b1d9b4b

Browse files
authored
Adds use for IAsyncDisposable to async varieties (#212)
1 parent d63b21f commit b1d9b4b

File tree

7 files changed

+655
-148
lines changed

7 files changed

+655
-148
lines changed

src/FsToolkit.ErrorHandling/Async.fs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,48 @@ module AsyncOperators =
8989
([<InlineIfLambda>] binder: 'input -> Async<'output>)
9090
: Async<'output> =
9191
Async.bind binder input
92+
93+
94+
[<AutoOpen>]
95+
module AsyncExt =
96+
open System
97+
98+
type Microsoft.FSharp.Control.Async with
99+
100+
static member TryFinallyAsync(comp: Async<'T>, deferred) : Async<'T> =
101+
102+
let finish (compResult, deferredResult) (cont, (econt: exn -> unit), ccont) =
103+
match (compResult, deferredResult) with
104+
| (Choice1Of3 x, Choice1Of3()) -> cont x
105+
| (Choice2Of3 compExn, Choice1Of3()) -> econt compExn
106+
| (Choice3Of3 compExn, Choice1Of3()) -> ccont compExn
107+
| (Choice1Of3 _, Choice2Of3 deferredExn) -> econt deferredExn
108+
| (Choice2Of3 compExn, Choice2Of3 deferredExn) ->
109+
econt
110+
<| new AggregateException(compExn, deferredExn)
111+
| (Choice3Of3 compExn, Choice2Of3 deferredExn) -> econt deferredExn
112+
| (_, Choice3Of3 deferredExn) ->
113+
econt
114+
<| new Exception("Unexpected cancellation.", deferredExn)
115+
116+
let startDeferred compResult (cont, econt, ccont) =
117+
Async.StartWithContinuations(
118+
deferred,
119+
(fun () -> finish (compResult, Choice1Of3()) (cont, econt, ccont)),
120+
(fun exn -> finish (compResult, Choice2Of3 exn) (cont, econt, ccont)),
121+
(fun exn -> finish (compResult, Choice3Of3 exn) (cont, econt, ccont))
122+
)
123+
124+
let startComp ct (cont, econt, ccont) =
125+
Async.StartWithContinuations(
126+
comp,
127+
(fun x -> startDeferred (Choice1Of3(x)) (cont, econt, ccont)),
128+
(fun exn -> startDeferred (Choice2Of3 exn) (cont, econt, ccont)),
129+
(fun exn -> startDeferred (Choice3Of3 exn) (cont, econt, ccont)),
130+
ct
131+
)
132+
133+
async {
134+
let! ct = Async.CancellationToken
135+
return! Async.FromContinuations(startComp ct)
136+
}

src/FsToolkit.ErrorHandling/AsyncOptionCE.fs

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,43 @@ module AsyncOptionCE =
4646
) : Async<'value option> =
4747
async.TryFinally(computation, compensation)
4848

49+
50+
#if NETSTANDARD2_1
51+
52+
member inline _.TryFinallyAsync
53+
(
54+
computation: Async<'value option>,
55+
[<InlineIfLambda>] compensation: unit -> ValueTask
56+
) : Async<'value option> =
57+
let compensation = async {
58+
let vTask = compensation ()
59+
60+
if vTask.IsCompletedSuccessfully then
61+
return ()
62+
else
63+
return!
64+
vTask.AsTask()
65+
|> Async.AwaitTask
66+
}
67+
68+
Async.TryFinallyAsync(computation, compensation)
69+
70+
4971
member inline this.Using
5072
(
51-
resource: 'disposable :> IDisposable,
52-
[<InlineIfLambda>] binder: 'disposable -> Async<'output option>
53-
) : Async<'output option> =
54-
this.TryFinally(
55-
(binder resource),
73+
resource: 'ok :> IAsyncDisposable,
74+
[<InlineIfLambda>] binder: 'ok -> Async<'value option>
75+
) : Async<'value option> =
76+
this.TryFinallyAsync(
77+
binder resource,
5678
(fun () ->
57-
if not (obj.ReferenceEquals(resource, null)) then
58-
resource.Dispose()
79+
if not (isNull (box resource)) then
80+
resource.DisposeAsync()
81+
else
82+
ValueTask()
5983
)
6084
)
85+
#endif
6186

6287
member this.While
6388
(
@@ -69,15 +94,6 @@ module AsyncOptionCE =
6994
else
7095
this.Bind(computation, (fun () -> this.While(guard, computation)))
7196

72-
member inline this.For
73-
(
74-
sequence: #seq<'input>,
75-
binder: 'input -> Async<unit option>
76-
) : Async<unit option> =
77-
this.Using(
78-
sequence.GetEnumerator(),
79-
fun enum -> this.While(enum.MoveNext, this.Delay(fun () -> binder enum.Current))
80-
)
8197

8298
/// <summary>
8399
/// Method lets us transform data types into our internal representation. This is the identity method to recognize the self type.
@@ -93,6 +109,7 @@ module AsyncOptionCE =
93109
member inline _.Source(task: Task<'value option>) : Async<'value option> =
94110
task
95111
|> Async.AwaitTask
112+
96113
#endif
97114

98115
let asyncOption = AsyncOptionBuilder()
@@ -122,6 +139,30 @@ module AsyncOptionCEExtensions =
122139
a
123140
|> Async.map Some
124141

142+
member inline this.Using
143+
(
144+
resource: 'disposable :> IDisposable,
145+
[<InlineIfLambda>] binder: 'disposable -> Async<'output option>
146+
) : Async<'output option> =
147+
this.TryFinally(
148+
(binder resource),
149+
(fun () ->
150+
if not (obj.ReferenceEquals(resource, null)) then
151+
resource.Dispose()
152+
)
153+
)
154+
155+
member inline this.For
156+
(
157+
sequence: #seq<'input>,
158+
binder: 'input -> Async<unit option>
159+
) : Async<unit option> =
160+
this.Using(
161+
sequence.GetEnumerator(),
162+
fun enum -> this.While(enum.MoveNext, this.Delay(fun () -> binder enum.Current))
163+
)
164+
165+
125166
#if !FABLE_COMPILER
126167
/// <summary>
127168
/// Method lets us transform data types into our internal representation.
@@ -130,4 +171,12 @@ module AsyncOptionCEExtensions =
130171
a
131172
|> Async.AwaitTask
132173
|> Async.map Some
174+
175+
/// <summary>
176+
/// Method lets us transform data types into our internal representation.
177+
/// </summary>
178+
member inline _.Source(a: Task) : Async<option<unit>> =
179+
a
180+
|> Async.AwaitTask
181+
|> Async.map Some
133182
#endif

src/FsToolkit.ErrorHandling/AsyncResultCE.fs

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,43 @@ module AsyncResultCE =
5454
) : Async<Result<'ok, 'error>> =
5555
async.TryFinally(computation, compensation)
5656

57-
member inline _.Using
57+
#if NETSTANDARD2_1
58+
59+
member inline _.TryFinallyAsync
5860
(
59-
resource: 'ok :> IDisposable,
61+
computation: Async<Result<'ok, 'error>>,
62+
[<InlineIfLambda>] compensation: unit -> ValueTask
63+
) : Async<Result<'ok, 'error>> =
64+
let compensation = async {
65+
let vTask = compensation ()
66+
67+
if vTask.IsCompletedSuccessfully then
68+
return ()
69+
else
70+
return!
71+
vTask.AsTask()
72+
|> Async.AwaitTask
73+
}
74+
75+
Async.TryFinallyAsync(computation, compensation)
76+
77+
78+
member inline this.Using
79+
(
80+
resource: 'ok :> IAsyncDisposable,
6081
[<InlineIfLambda>] binder: 'ok -> Async<Result<'U, 'error>>
6182
) : Async<Result<'U, 'error>> =
62-
async.Using(resource, binder)
83+
this.TryFinallyAsync(
84+
binder resource,
85+
(fun () ->
86+
if not (isNull (box resource)) then
87+
resource.DisposeAsync()
88+
else
89+
ValueTask()
90+
)
91+
)
92+
#endif
93+
6394

6495
member inline this.While
6596
(
@@ -77,20 +108,6 @@ module AsyncResultCE =
77108
this.Zero()
78109

79110

80-
member inline this.For
81-
(
82-
sequence: #seq<'ok>,
83-
[<InlineIfLambda>] binder: 'ok -> Async<Result<unit, 'error>>
84-
) : Async<Result<unit, 'error>> =
85-
this.Using(
86-
sequence.GetEnumerator(),
87-
fun enum ->
88-
this.While(
89-
(fun () -> enum.MoveNext()),
90-
this.Delay(fun () -> binder enum.Current)
91-
)
92-
)
93-
94111
member inline _.BindReturn
95112
(
96113
x: Async<Result<'okInput, 'error>>,
@@ -156,6 +173,30 @@ module AsyncResultCEExtensions =
156173
asyncComputation
157174
|> Async.map Ok
158175

176+
177+
member inline _.Using
178+
(
179+
resource: 'ok :> IDisposable,
180+
[<InlineIfLambda>] binder: 'ok -> Async<Result<'U, 'error>>
181+
) : Async<Result<'U, 'error>> =
182+
async.Using(resource, binder)
183+
184+
185+
member inline this.For
186+
(
187+
sequence: #seq<'ok>,
188+
[<InlineIfLambda>] binder: 'ok -> Async<Result<unit, 'error>>
189+
) : Async<Result<unit, 'error>> =
190+
this.Using(
191+
sequence.GetEnumerator(),
192+
fun enum ->
193+
this.While(
194+
(fun () -> enum.MoveNext()),
195+
this.Delay(fun () -> binder enum.Current)
196+
)
197+
)
198+
199+
159200
#if !FABLE_COMPILER
160201
/// <summary>
161202
/// Method lets us transform data types into our internal representation.

src/FsToolkit.ErrorHandling/AsyncResultOptionCE.fs

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,42 @@ module AsyncResultOptionCE =
4545
async.TryFinally(computation, compensation)
4646

4747

48-
member inline _.Using
48+
#if NETSTANDARD2_1
49+
50+
member inline _.TryFinallyAsync
4951
(
50-
resource: 'ok :> IDisposable,
51-
[<InlineIfLambda>] binder: 'ok -> AsyncResultOption<'okOutput, 'error>
52-
) : AsyncResultOption<'okOutput, 'error> =
53-
async.Using(resource, binder)
52+
computation: AsyncResultOption<'ok, 'error>,
53+
[<InlineIfLambda>] compensation: unit -> ValueTask
54+
) : AsyncResultOption<'ok, 'error> =
55+
let compensation = async {
56+
let vTask = compensation ()
57+
58+
if vTask.IsCompletedSuccessfully then
59+
return ()
60+
else
61+
return!
62+
vTask.AsTask()
63+
|> Async.AwaitTask
64+
}
65+
66+
Async.TryFinallyAsync(computation, compensation)
67+
68+
69+
member inline this.Using
70+
(
71+
resource: 'ok :> IAsyncDisposable,
72+
[<InlineIfLambda>] binder: 'ok -> AsyncResultOption<'okOut, 'error>
73+
) : AsyncResultOption<'okOut, 'error> =
74+
this.TryFinallyAsync(
75+
binder resource,
76+
(fun () ->
77+
if not (isNull (box resource)) then
78+
resource.DisposeAsync()
79+
else
80+
ValueTask()
81+
)
82+
)
83+
#endif
5484

5585

5686
member inline this.While
@@ -68,21 +98,6 @@ module AsyncResultOptionCE =
6898
else
6999
this.Zero()
70100

71-
72-
member inline this.For
73-
(
74-
sequence: #seq<'ok>,
75-
[<InlineIfLambda>] binder: 'ok -> AsyncResultOption<unit, 'error>
76-
) : AsyncResultOption<unit, 'error> =
77-
this.Using(
78-
sequence.GetEnumerator(),
79-
fun enum ->
80-
this.While(
81-
(fun () -> enum.MoveNext()),
82-
this.Delay(fun () -> binder enum.Current)
83-
)
84-
)
85-
86101
/// <summary>
87102
/// Method lets us transform data types into our internal representation. This is the identity method to recognize the self type.
88103
///
@@ -142,6 +157,30 @@ module AsyncResultOptionCEExtensions =
142157
member inline this.Source(async: Async<'ok>) : AsyncResultOption<'ok, 'error> =
143158
Async.map (Some >> Ok) async
144159

160+
161+
member inline _.Using
162+
(
163+
resource: 'ok :> IDisposable,
164+
[<InlineIfLambda>] binder: 'ok -> AsyncResultOption<'okOutput, 'error>
165+
) : AsyncResultOption<'okOutput, 'error> =
166+
async.Using(resource, binder)
167+
168+
169+
member inline this.For
170+
(
171+
sequence: #seq<'ok>,
172+
[<InlineIfLambda>] binder: 'ok -> AsyncResultOption<unit, 'error>
173+
) : AsyncResultOption<unit, 'error> =
174+
this.Using(
175+
sequence.GetEnumerator(),
176+
fun enum ->
177+
this.While(
178+
(fun () -> enum.MoveNext()),
179+
this.Delay(fun () -> binder enum.Current)
180+
)
181+
)
182+
183+
145184
#if !FABLE_COMPILER
146185
/// <summary>
147186
/// Method lets us transform data types into our internal representation.

0 commit comments

Comments
 (0)