diff --git a/src/fsharp/FSharp.Core/async.fs b/src/fsharp/FSharp.Core/async.fs index 4224440e6f1..685e67f9cd1 100644 --- a/src/fsharp/FSharp.Core/async.fs +++ b/src/fsharp/FSharp.Core/async.fs @@ -871,11 +871,9 @@ namespace Microsoft.FSharp.Control [] let RunSynchronously cancellationToken (computation: Async<'T>) timeout = - // Reuse the current ThreadPool thread if possible. Unfortunately - // Thread.IsThreadPoolThread isn't available on all profiles so - // we approximate it by testing synchronization context for null. - match SynchronizationContext.Current, timeout with - | null, None -> RunSynchronouslyInCurrentThread (cancellationToken, computation) + // Reuse the current ThreadPool thread if possible. + match Thread.CurrentThread.IsThreadPoolThread, timeout with + | true, None -> RunSynchronouslyInCurrentThread (cancellationToken, computation) // When the timeout is given we need a dedicated thread // which cancels the computation. // Performing the cancellation in the ThreadPool eg. by using @@ -941,7 +939,11 @@ namespace Microsoft.FSharp.Control else ctxt.cont completedTask.Result) |> unfake - task.ContinueWith(Action>(continuation)) |> ignore |> fake + if task.IsCompleted then + continuation task |> fake + else + task.ContinueWith(Action>(continuation), TaskContinuationOptions.ExecuteSynchronously) + |> ignore |> fake [] let taskContinueWithUnit (task: Task) (ctxt: AsyncActivation) useCcontForTaskCancellation = @@ -960,7 +962,11 @@ namespace Microsoft.FSharp.Control else ctxt.cont ()) |> unfake - task.ContinueWith(Action(continuation)) |> ignore |> fake + if task.IsCompleted then + continuation task |> fake + else + task.ContinueWith(Action(continuation), TaskContinuationOptions.ExecuteSynchronously) + |> ignore |> fake [] type AsyncIAsyncResult<'T>(callback: System.AsyncCallback, state:obj) = @@ -1692,10 +1698,16 @@ namespace Microsoft.FSharp.Control CreateWhenCancelledAsync compensation computation static member AwaitTask (task:Task<'T>) : Async<'T> = - CreateDelimitedUserCodeAsync (fun ctxt -> taskContinueWith task ctxt false) + if task.IsCompleted then + CreateProtectedAsync (fun ctxt -> taskContinueWith task ctxt false) + else + CreateDelimitedUserCodeAsync (fun ctxt -> taskContinueWith task ctxt false) static member AwaitTask (task:Task) : Async = - CreateDelimitedUserCodeAsync (fun ctxt -> taskContinueWithUnit task ctxt false) + if task.IsCompleted then + CreateProtectedAsync (fun ctxt -> taskContinueWithUnit task ctxt false) + else + CreateDelimitedUserCodeAsync (fun ctxt -> taskContinueWithUnit task ctxt false) module CommonExtensions = diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs index c89f5913c1f..2056a83f361 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs @@ -45,7 +45,7 @@ type AsyncType() = let onSuccess x = match !whatToDo with - | Cancel | Throw + | Cancel | Throw -> Assert.Fail("Expected onSuccess but whatToDo was not Exit", [| whatToDo |]) | Exit -> () @@ -167,8 +167,8 @@ type AsyncType() = member private this.WaitASec (t:Task) = let result = t.Wait(TimeSpan(hours=0,minutes=0,seconds=1)) Assert.True(result, "Task did not finish after waiting for a second.") - - + + [] member this.CreateTask () = let s = "Hello tasks!" @@ -206,7 +206,7 @@ type AsyncType() = with :? AggregateException -> Assert.Fail "Task should not finish, yet" spinloop <- false - + try this.WaitASec t with :? AggregateException as a -> @@ -257,47 +257,47 @@ type AsyncType() = [] member this.ExceptionPropagatesToTask () = - let a = async { + let a = async { do raise (Exception ()) } #if !NET46 - let t = + let t = #else use t = #endif Async.StartAsTask a let mutable exceptionThrown = false - try + try this.WaitASec t - with + with e -> exceptionThrown <- true Assert.True (t.IsFaulted) Assert.True(exceptionThrown) - + [] member this.CancellationPropagatesToTask () = let a = async { while true do () } #if !NET46 - let t = + let t = #else use t = #endif Async.StartAsTask a - Async.CancelDefaultToken () + Async.CancelDefaultToken () let mutable exceptionThrown = false try this.WaitASec t with e -> exceptionThrown <- true - Assert.True (exceptionThrown) - Assert.True(t.IsCanceled) - + Assert.True (exceptionThrown) + Assert.True(t.IsCanceled) + [] member this.CancellationPropagatesToGroup () = let ewh = new ManualResetEvent(false) let cancelled = ref false - let a = async { + let a = async { use! holder = Async.OnCancel (fun _ -> cancelled := true) ewh.Set() |> Assert.True while true do () @@ -305,7 +305,7 @@ type AsyncType() = let cts = new CancellationTokenSource() let token = cts.Token #if !NET46 - let t = + let t = #else use t = #endif @@ -313,14 +313,14 @@ type AsyncType() = // printfn "%A" t.Status ewh.WaitOne() |> Assert.True cts.Cancel() -// printfn "%A" t.Status +// printfn "%A" t.Status let mutable exceptionThrown = false try this.WaitASec t with e -> exceptionThrown <- true - Assert.True (exceptionThrown) - Assert.True(t.IsCanceled) - Assert.True(!cancelled) + Assert.True (exceptionThrown) + Assert.True(t.IsCanceled) + Assert.True(!cancelled) [] member this.CreateImmediateAsTask () = @@ -334,38 +334,38 @@ type AsyncType() = Async.StartImmediateAsTask a this.WaitASec t Assert.True (t.IsCompleted) - Assert.AreEqual(s, t.Result) - + Assert.AreEqual(s, t.Result) + [] member this.StartImmediateAsTask () = let s = "Hello tasks!" let a = async { return s } #if !NET46 - let t = + let t = #else use t = #endif Async.StartImmediateAsTask a this.WaitASec t Assert.True (t.IsCompleted) - Assert.AreEqual(s, t.Result) + Assert.AreEqual(s, t.Result) + - [] member this.ExceptionPropagatesToImmediateTask () = - let a = async { + let a = async { do raise (Exception ()) } #if !NET46 - let t = + let t = #else use t = #endif Async.StartImmediateAsTask a let mutable exceptionThrown = false - try + try this.WaitASec t - with + with e -> exceptionThrown <- true Assert.True (t.IsFaulted) Assert.True(exceptionThrown) @@ -378,18 +378,18 @@ type AsyncType() = while true do () } #if !NET46 - let t = + let t = #else use t = #endif Async.StartImmediateAsTask a - Async.CancelDefaultToken () + Async.CancelDefaultToken () let mutable exceptionThrown = false try this.WaitASec t with e -> exceptionThrown <- true - Assert.True (exceptionThrown) - Assert.True(t.IsCanceled) + Assert.True (exceptionThrown) + Assert.True(t.IsCanceled) #endif #if IGNORED @@ -398,7 +398,7 @@ type AsyncType() = member this.CancellationPropagatesToGroupImmediate () = let ewh = new ManualResetEvent(false) let cancelled = ref false - let a = async { + let a = async { use! holder = Async.OnCancel (fun _ -> cancelled := true) ewh.Set() |> Assert.True while true do () @@ -410,21 +410,21 @@ type AsyncType() = // printfn "%A" t.Status ewh.WaitOne() |> Assert.True cts.Cancel() -// printfn "%A" t.Status +// printfn "%A" t.Status let mutable exceptionThrown = false try this.WaitASec t with e -> exceptionThrown <- true - Assert.True (exceptionThrown) - Assert.True(t.IsCanceled) - Assert.True(!cancelled) + Assert.True (exceptionThrown) + Assert.True(t.IsCanceled) + Assert.True(!cancelled) #endif [] member this.TaskAsyncValue () = let s = "Test" #if !NET46 - let t = + let t = #else use t = #endif @@ -433,38 +433,49 @@ type AsyncType() = let! s1 = Async.AwaitTask(t) return s = s1 } - Async.RunSynchronously(a, 1000) |> Assert.True + Async.RunSynchronously(a, 1000) |> Assert.True [] member this.AwaitTaskCancellation () = let test() = async { let tcs = new System.Threading.Tasks.TaskCompletionSource() tcs.SetCanceled() - try + try do! Async.AwaitTask tcs.Task return false with :? System.OperationCanceledException -> return true } - Async.RunSynchronously(test()) |> Assert.True - + Async.RunSynchronously(test()) |> Assert.True + + [] + member this.AwaitCompletedTask() = + let test() = async { + let threadIdBefore = Thread.CurrentThread.ManagedThreadId + do! Async.AwaitTask Task.CompletedTask + let threadIdAfter = Thread.CurrentThread.ManagedThreadId + return threadIdBefore = threadIdAfter + } + + Async.RunSynchronously(test()) |> Assert.True + [] member this.AwaitTaskCancellationUntyped () = let test() = async { let tcs = new System.Threading.Tasks.TaskCompletionSource() tcs.SetCanceled() - try + try do! Async.AwaitTask (tcs.Task :> Task) return false with :? System.OperationCanceledException -> return true } - Async.RunSynchronously(test()) |> Assert.True - + Async.RunSynchronously(test()) |> Assert.True + [] member this.TaskAsyncValueException () = #if !NET46 - let t = + let t = #else use t = #endif @@ -475,37 +486,37 @@ type AsyncType() = return false with e -> return true } - Async.RunSynchronously(a, 1000) |> Assert.True - + Async.RunSynchronously(a, 1000) |> Assert.True + [] member this.TaskAsyncValueCancellation () = - use ewh = new ManualResetEvent(false) + use ewh = new ManualResetEvent(false) let cts = new CancellationTokenSource() let token = cts.Token #if !NET46 - let t : Task= + let t : Task= #else use t : Task= -#endif +#endif Task.Factory.StartNew(Func(fun () -> while not token.IsCancellationRequested do ()), token) let cancelled = ref true let a = async { use! _holder = Async.OnCancel(fun _ -> ewh.Set() |> ignore) let! v = Async.AwaitTask(t) return v - } + } Async.Start a cts.Cancel() - ewh.WaitOne(10000) |> ignore + ewh.WaitOne(10000) |> ignore [] member this.NonGenericTaskAsyncValue () = let hasBeenCalled = ref false #if !NET46 - let t = + let t = #else use t = -#endif +#endif Task.Factory.StartNew(Action(fun () -> hasBeenCalled := true)) let a = async { do! Async.AwaitTask(t) @@ -513,14 +524,14 @@ type AsyncType() = } let result =Async.RunSynchronously(a, 1000) (!hasBeenCalled && result) |> Assert.True - + [] member this.NonGenericTaskAsyncValueException () = #if !NET46 - let t = + let t = #else use t = -#endif +#endif Task.Factory.StartNew(Action(fun () -> raise <| Exception())) let a = async { try @@ -528,8 +539,8 @@ type AsyncType() = return false with e -> return true } - Async.RunSynchronously(a, 3000) |> Assert.True - + Async.RunSynchronously(a, 3000) |> Assert.True + [] member this.NonGenericTaskAsyncValueCancellation () = use ewh = new ManualResetEvent(false)