Skip to content

Commit

Permalink
Add (and use internally) Async.Await (#591)
Browse files Browse the repository at this point in the history
  • Loading branch information
gusty authored Feb 5, 2024
1 parent 464cf28 commit 37df918
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 17 deletions.
16 changes: 8 additions & 8 deletions src/FSharpPlus/Extensions/Async.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace FSharpPlus
[<RequireQualifiedAccess>]
module Async =

open System
open FSharpPlus.Extensions

/// <summary>Creates an async workflow from another workflow 'x', mapping its result with 'f'.</summary>
let map f x = async.Bind (x, async.Return << f)
Expand Down Expand Up @@ -43,8 +43,8 @@ module Async =
let! ct = Async.CancellationToken
let x = Async.StartImmediateAsTask (x, ct)
let y = Async.StartImmediateAsTask (y, ct)
let! x' = Async.AwaitTask x
let! y' = Async.AwaitTask y
let! x' = Async.Await x
let! y' = Async.Await y
return f x' y' }
#endif

Expand All @@ -62,9 +62,9 @@ module Async =
let x = Async.StartImmediateAsTask (x, ct)
let y = Async.StartImmediateAsTask (y, ct)
let z = Async.StartImmediateAsTask (z, ct)
let! x' = Async.AwaitTask x
let! y' = Async.AwaitTask y
let! z' = Async.AwaitTask z
let! x' = Async.Await x
let! y' = Async.Await y
let! z' = Async.Await z
return f x' y' z' }
#endif

Expand All @@ -83,8 +83,8 @@ module Async =
let! ct = Async.CancellationToken
let x = Async.StartImmediateAsTask (x, ct)
let y = Async.StartImmediateAsTask (y, ct)
let! x' = Async.AwaitTask x
let! y' = Async.AwaitTask y
let! x' = Async.Await x
let! y' = Async.Await y
return x', y' }
#endif

Expand Down
5 changes: 3 additions & 2 deletions src/FSharpPlus/Extensions/AsyncEnumerable.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ open System
open System.Threading
open System.Threading.Tasks
open FSharpPlus.Data
open FSharpPlus.Extensions

/// Additional operations on Observable<'T>
[<RequireQualifiedAccess>]
Expand All @@ -17,11 +18,11 @@ module AsyncEnumerable =
use _ =
{ new IDisposable with
member _.Dispose () =
e.DisposeAsync().AsTask () |> Async.AwaitTask |> Async.RunSynchronously }
e.DisposeAsync().AsTask () |> Async.Await |> Async.RunSynchronously }

let mutable currentResult = true
while currentResult do
let! r = e.MoveNextAsync().AsTask () |> Async.AwaitTask |> SeqT.lift
let! r = e.MoveNextAsync().AsTask () |> Async.Await |> SeqT.lift
currentResult <- r
if r then yield e.Current
}
Expand Down
72 changes: 66 additions & 6 deletions src/FSharpPlus/Extensions/Extensions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,67 @@ module Extensions =

type Async<'t> with

static member internal Map f x = async.Bind (x, async.Return << f)

#if !FABLE_COMPILER

// See https://github.com/fsharp/fslang-suggestions/issues/840

/// <summary>Return an asynchronous computation that will wait for the given task to complete and return
/// its result.</summary>
///
/// <param name="task">The task to await.</param>
///
/// <remarks>Prefer this over <c>Async.AwaitTask</c>.
///
/// If an exception occurs in the asynchronous computation then an exception is re-raised by this function.
///
/// If the task is cancelled then <see cref="F:System.Threading.Tasks.TaskCanceledException"/> is raised. Note
/// that the task may be governed by a different cancellation token to the overall async computation where the
/// Await occurs. In practice you should normally start the task with the cancellation token returned by
/// <c>let! ct = Async.CancellationToken</c>, and catch any <see cref="F:System.Threading.Tasks.TaskCanceledException"/>
/// at the point where the overall async is started.
/// </remarks>
static member Await (task: Task<'T>) : Async<'T> =
Async.FromContinuations (fun (sc, ec, _) ->
task.ContinueWith (fun (task: Task<'T>) ->
if task.IsFaulted then
let e = task.Exception
if e.InnerExceptions.Count = 1 then ec e.InnerExceptions[0]
else ec e
elif task.IsCanceled then ec (TaskCanceledException ())
else sc task.Result)
|> ignore)


/// <summary>Return an asynchronous computation that will wait for the given task to complete and return
/// its result.</summary>
///
/// <param name="task">The task to await.</param>
///
/// <remarks>Prefer this over <c>Async.AwaitTask</c>.
///
/// If an exception occurs in the asynchronous computation then an exception is re-raised by this function.
///
/// If the task is cancelled then <see cref="F:System.Threading.Tasks.TaskCanceledException"/> is raised. Note
/// that the task may be governed by a different cancellation token to the overall async computation where the
/// Await occurs. In practice you should normally start the task with the cancellation token returned by
/// <c>let! ct = Async.CancellationToken</c>, and catch any <see cref="F:System.Threading.Tasks.TaskCanceledException"/>
/// at the point where the overall async is started.
/// </remarks>
static member Await (task: Task) : Async<unit> =
Async.FromContinuations (fun (sc, ec, _) ->
task.ContinueWith (fun (task: Task) ->
if task.IsFaulted then
let e = task.Exception
if e.InnerExceptions.Count = 1 then ec e.InnerExceptions[0]
else ec e
elif task.IsCanceled then ec (TaskCanceledException ())
else sc ())
|> ignore)



/// Combine all asyncs in one, chaining them in sequence order.
static member Sequence (t:seq<Async<_>>) : Async<seq<_>> = async {
let startImmediateAsTask ct a =
Expand Down Expand Up @@ -102,26 +162,26 @@ module Extensions =
/// Creates an async Result from a Result where the Ok case is async.
static member Sequence (t: Result<Async<'T>, 'Error>) : Async<Result<'T,'Error>> =
match t with
| Ok a -> Async.map Ok a
| Ok a -> Async.Map Ok a
| Error e -> async.Return (Error e)

/// Creates an async Choice from a Choice where the Choice1Of2 case is async.
static member Sequence (t: Choice<Async<'T>, 'Choice2Of2>) : Async<Choice<'T,'Choice2Of2>> =
match t with
| Choice1Of2 a -> Async.map Choice1Of2 a
| Choice1Of2 a -> Async.Map Choice1Of2 a
| Choice2Of2 e -> async.Return (Choice2Of2 e)

/// Creates an async Result from a Result where both cases are async.
static member Bisequence (t: Result<Async<'T>, Async<'Error>>) : Async<Result<'T,'Error>> =
match t with
| Ok a -> Async.map Ok a
| Error e -> Async.map Error e
| Ok a -> Async.Map Ok a
| Error e -> Async.Map Error e

/// Creates an async Choice from a Choice where both cases are async.
static member Bisequence (t: Choice<Async<'T>, Async<'Choice2Of2>>) : Async<Choice<'T,'Choice2Of2>> =
match t with
| Choice1Of2 a -> Async.map Choice1Of2 a
| Choice2Of2 e -> Async.map Choice2Of2 e
| Choice1Of2 a -> Async.Map Choice1Of2 a
| Choice2Of2 e -> Async.Map Choice2Of2 e

type Option<'t> with

Expand Down
2 changes: 1 addition & 1 deletion src/FSharpPlus/FSharpPlus.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@
<Compile Include="Extensions/Enumerator.fs" />
<Compile Include="Extensions/Task.fs" />
<Compile Include="Extensions/ValueTask.fs" />
<Compile Include="Extensions/Async.fs" />
<Compile Include="Extensions/Extensions.fs" />
<Compile Include="Extensions/Async.fs" />
<Compile Include="Extensions/Tuple.fs" />
<Compile Include="Extensions/ValueTuple.fs" />
<Compile Include="Data/NonEmptySeq.fs" />
Expand Down

0 comments on commit 37df918

Please sign in to comment.