diff --git a/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs b/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs index 688f72da86..7310ef8847 100644 --- a/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs +++ b/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs @@ -132,7 +132,7 @@ type AsyncType() = member private this.WaitASec (t:Task) = let result = t.Wait(TimeSpan(hours=0,minutes=0,seconds=1)) - Assert.IsTrue(result) + Assert.IsTrue(result, "Task did not finish after waiting for a second.") [] @@ -147,8 +147,39 @@ type AsyncType() = Async.StartAsTask a this.WaitASec t Assert.IsTrue (t.IsCompleted) - Assert.AreEqual(s, t.Result) + Assert.AreEqual(s, t.Result) + + [] + member this.StartAsTaskCancellation () = + let cts = new CancellationTokenSource() + let tcs = TaskCompletionSource() + let a = async { + cts.CancelAfter (100) + do! tcs.Task |> Async.AwaitTask } +#if FSCORE_PORTABLE_NEW || coreclr + let t : Task = +#else + use t : Task = +#endif + Async.StartAsTask(a, cancellationToken = cts.Token) + + // Should not finish + try + let result = t.Wait(300) + Assert.IsFalse (result) + with :? AggregateException -> Assert.Fail "Task should not finish, jet" + + tcs.SetCanceled() + try + this.WaitASec t + with :? AggregateException as a -> + match a.InnerException with + | :? TaskCanceledException as t -> () + | _ -> reraise() + System.Diagnostics.Debugger.Break() |> ignore + Assert.IsTrue (t.IsCompleted, "Task is not completed") + [] member this.StartTask () = let s = "Hello tasks!" diff --git a/src/fsharp/FSharp.Core/control.fs b/src/fsharp/FSharp.Core/control.fs index a3427c7ce3..9abf5e271a 100644 --- a/src/fsharp/FSharp.Core/control.fs +++ b/src/fsharp/FSharp.Core/control.fs @@ -974,33 +974,16 @@ namespace Microsoft.FSharp.Control let tcs = new TaskCompletionSource<_>(taskCreationOptions) // The contract: - // a) cancellation signal should always propagate to task - // b) CancellationTokenSource that produced a token must not be disposed until the task.IsComplete - // We are: - // 1) registering for cancellation signal here so that not to miss the signal - // 2) disposing the registration just before setting result/exception on TaskCompletionSource - - // otherwise we run a chance of disposing registration on already disposed CancellationTokenSource - // (See (b) above) - // 3) ensuring if reg is disposed, we do SetResult - let barrier = VolatileBarrier() - let reg = token.Register(fun _ -> if barrier.Proceed then tcs.SetCanceled()) + // a) cancellation signal should always propagate to the computation + // b) when the task IsCompleted -> nothing is running anymore let task = tcs.Task - let disposeReg() = - barrier.Stop() - if not (task.IsCanceled) then reg.Dispose() - - let a = - async { - try - let! result = computation - do - disposeReg() - tcs.TrySetResult(result) |> ignore - with exn -> - disposeReg() - tcs.TrySetException(exn) |> ignore - } - Start(token, a) + queueAsync + token + (fun r -> tcs.SetResult r |> fake) + (fun edi -> tcs.SetException edi.SourceException |> fake) + (fun _ -> tcs.SetCanceled() |> fake) + computation + |> unfake task []