Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions docs/release-notes/.FSharp.Core/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,23 @@
* Fix array and string slices with extreme reversed bounds to return correctly shaped empty results. ([Issue #20530](https://github.com/dotnet/fsharp/issues/20530), [PR #20557](https://github.com/dotnet/fsharp/pull/20557))
* Fix printf handling of -0.0 (negative zero) values for float, float32, and decimal values ([Issue #15557](https://github.com/dotnet/fsharp/issues/15557) and [Issue #15558](https://github.com/dotnet/fsharp/issues/15558), [PR #18147](https://github.com/dotnet/fsharp/pull/18147))


### Added

* Add the compiler-recognized `StateMachineHelpers.__runtimeAsyncReturn` intrinsic to the `net10.0` FSharp.Core target for .NET runtime-async methods. ([PR #20235](https://github.com/dotnet/fsharp/pull/20235))
* Add `Unchecked.withNull`, an interop escape hatch that re-types any `'T` to `'T | null` without the usual `not null`/`not struct` constraints, so unconstrained C# nullable-generic APIs (e.g. `T? M<T>()`) can be implemented and consumed from F#. ([Issue #17734](https://github.com/dotnet/fsharp/issues/17734), [PR #20232](https://github.com/dotnet/fsharp/pull/20232))
* Added generic `print` and `printn` functions (`'T -> unit`) to `ExtraTopLevelOperators` for simple value printing to stdout. ([RFC FS-1125](https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-1125-print-printn-functions.md), [PR #19265](https://github.com/dotnet/fsharp/pull/19265))
* Add generic `print` and `printn` functions (`'T -> unit`) to `ExtraTopLevelOperators` for simple value printing to stdout. ([RFC FS-1125](https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-1125-print-printn-functions.md), [PR #19265](https://github.com/dotnet/fsharp/pull/19265))
* Ship `FSharp.Core` with an additional `net10.0` target framework (next to `netstandard2.0` and `netstandard2.1`). The `net`-TFM assembly is public-surface-identical to the `netstandard2.1` one; the target version is a pinned, deliberately advanced knob. ([PR #20229](https://github.com/dotnet/fsharp/pull/20229))
* Add `Async.Await`, mirroring `Async.AwaitTask` semantics, but elides egregious `AggregateException` wrapping. Includes `ValueTask` support, and a SRTP-based overload accepting any Task-like value that supports the `GetAwaiter` protocol. ([Language Suggestion #840](https://github.com/fsharp/fslang-suggestions/issues/840), [PR #19785](https://github.com/dotnet/fsharp/pull/19785))
* `Async.RunSynchronouslyImmediate`: runs work on the calling thread until the first asynchronous suspension (as opposed to `RunSynchronously`, which immediately offloads if not on a background and/or threadpool thread). ([Issue #1042](https://github.com/fsharp/fslang-suggestions/issues/1042), [PR #19804](https://github.com/dotnet/fsharp/pull/19804))
* Added `AllowOverloadOnReturnTypeAttribute`: applying it to a method causes its return type to be considered during overload resolution, enabling overloads that differ only by return type. ([fslang-suggestions#820](https://github.com/fsharp/fslang-suggestions/issues/820), [PR #19602](https://github.com/dotnet/fsharp/pull/19602))
* Add `Async.RunSynchronouslyImmediate`: runs work on the calling thread until the first asynchronous suspension (as opposed to `RunSynchronously`, which immediately offloads if not on a background and/or threadpool thread). ([Issue #1042](https://github.com/fsharp/fslang-suggestions/issues/1042), [PR #19804](https://github.com/dotnet/fsharp/pull/19804))
* Add `AllowOverloadOnReturnTypeAttribute`: applying it to a method causes its return type to be considered during overload resolution, enabling overloads that differ only by return type. ([fslang-suggestions#820](https://github.com/fsharp/fslang-suggestions/issues/820), [PR #19602](https://github.com/dotnet/fsharp/pull/19602))
* Add `Async.StartTaskImmediate`: passes the ambient `Async.CancellationToken` to a task factory, then await the result using `Async.Await` semantics. Overloads for `Task`, `Task<'T>`, `ValueTask`, `ValueTask<'T>` and task-like `.GetAwaiter()` (via SRTP). ([Language Suggestion #1284](https://github.com/fsharp/fslang-suggestions/issues/1284), [PR #20258](https://github.com/dotnet/fsharp/pull/20258))
* Add modules for `Async`, `Task` and `ValueTask` with consistent `result`, `map`, `bind`, `ignore`, `catchWith`, `catch`, and `empty` functions ([LanguageSuggestion #1466](https://github.com/fsharp/fslang-suggestions/issues/1466), [PR #19844](https://github.com/dotnet/fsharp/pull/19844))
* Add conversion functions `Task.ofValueTask` and `ValueTask.ofTask`. ([LanguageSuggestion #1466](https://github.com/fsharp/fslang-suggestions/issues/1466), [PR #19844](https://github.com/dotnet/fsharp/pull/19844))
* Add `parallelLimit` to `Task` and `Async` modules, for bounded-parallelism execution of async/task computations, flowing cancellation and returning the results as an array. ([LanguageSuggestion #1467](https://github.com/fsharp/fslang-suggestions/issues/1467), [PR #20294](https://github.com/dotnet/fsharp/pull/20294))
* Add `parallelDoLimit` to `Task` and `Async` modules, for bounded-parallelism execution of async/task computations, flowing cancellation. ([LanguageSuggestion #1467](https://github.com/fsharp/fslang-suggestions/issues/1467), [PR #20294](https://github.com/dotnet/fsharp/pull/20294))
* Prototype a compiler-services entry and reference host for direct runtime-async sequences on the `net10.0` target, reusing the first enumerator instance.
* Add `sequentialDo` to `Task` and `Async`, running computations one at a time, flowing cancellation, without yielding an egregious `unit[]`. ([LanguageSuggestion #1467](https://github.com/fsharp/fslang-suggestions/issues/1467), [PR #20294](https://github.com/dotnet/fsharp/pull/20294))
* Add `Task.sequential`, running task computations one at a time, flowing cancellation and returning the results as an array. ([LanguageSuggestion #1467](https://github.com/fsharp/fslang-suggestions/issues/1467), [PR #20294](https://github.com/dotnet/fsharp/pull/20294))
* Add `sequentialDo` to `Task` and `Async`, running computations one at a time, flowing cancellation, without yielding an egregious `unit[]`. ([LanguageSuggestion #1467](https://github.com/fsharp/fslang-suggestions/issues/1467), [PR #20294](https://github.com/dotnet/fsharp/pull/20294), [PR #20695](https://github.com/dotnet/fsharp/pull/20695))
* Add `Task.sequential`, running task computations one at a time, flowing cancellation and returning the results as an array. ([LanguageSuggestion #1467](https://github.com/fsharp/fslang-suggestions/issues/1467), [PR #20294](https://github.com/dotnet/fsharp/pull/20294), [PR #20695](https://github.com/dotnet/fsharp/pull/20695))
* Add `Task.startAsyncImmediate`, starting an `Async<'T>` on the current thread and returning a `Task<'T>`, flowing cancellation. ([LanguageSuggestion #1467](https://github.com/fsharp/fslang-suggestions/issues/1467), [PR #20294](https://github.com/dotnet/fsharp/pull/20294))

### Changed
Expand Down
2 changes: 1 addition & 1 deletion docs/release-notes/.VisualStudio/18.vNext.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

* Go To Definition no longer blocks the UI thread with a bare `Task.Wait`: the synchronous `IFSharpGoToDefinitionService` call now waits through the cancellable threaded-wait dialog, and the editor's `TaskCompletionSource` bridges run their continuations on the thread pool instead of inline on whichever thread finished the check, so repeated F12 on a large solution no longer starves semantic classification and other main-thread work. ([PR #20482](https://github.com/dotnet/fsharp/pull/20482))
* Peek Definition on an F# symbol whose definition lives in metadata no longer deadlocks Visual Studio. Peek holds the main thread in `JoinableTaskFactory.Run` without pumping messages while it asks the language service for the definition, and generating the metadata document needs that same thread; Peek now stops at definitions that already have a document, and Go To Definition, which owns the wait it makes, still opens the generated one. ([PR #20503](https://github.com/dotnet/fsharp/pull/20503))
* Improve Find All References performance by throttling parallel typechecks. ([PR #20128](https://github.com/dotnet/fsharp/pull/20128))
* Improve Find All References performance by throttling parallel typechecks. ([PR #20128](https://github.com/dotnet/fsharp/pull/20128), [PR #20695](https://github.com/dotnet/fsharp/pull/20695))
* Fixed Rename incorrectly renaming `get` and `set` keywords for properties with explicit accessors. ([Issue #18270](https://github.com/dotnet/fsharp/issues/18270), [PR #19252](https://github.com/dotnet/fsharp/pull/19252))
* Fixed Find All References crash when F# project contains non-F# files like `.cshtml`. ([Issue #16394](https://github.com/dotnet/fsharp/issues/16394), [PR #19252](https://github.com/dotnet/fsharp/pull/19252))
* Avoid using `cancellableTask` in `DocumentCache`; the editor cache now uses direct `CancellationToken`-aware `task` wrappers, avoiding the background `Task.Run` offload and a larger wrapper closure from the `cancellableTask` builder. ([Issue #20268](https://github.com/dotnet/fsharp/issues/20268))
Expand Down
2 changes: 2 additions & 0 deletions src/FSharp.Core/tasks.fs
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,7 @@ module Task =
let mutable results = ArrayCollector<'T>()

for f in computations do
ct.ThrowIfCancellationRequested()
let! result = f ct
results.Add result

Expand All @@ -805,6 +806,7 @@ module Task =
let sequentialDo (ct: CancellationToken) (computations: seq<CancellationToken -> Task<unit>>) : Task<unit> =
task {
for f in computations do
ct.ThrowIfCancellationRequested()
do! f ct
}

Expand Down
4 changes: 2 additions & 2 deletions src/FSharp.Core/tasks.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ module Task =
/// <param name="ct">A cancellation token to pass to each task factory.</param>
/// <param name="computations">A sequence of task start functions accepting a <see cref="T:System.Threading.CancellationToken"/>.</param>
/// <returns>A task yielding an array of the results of <c>computations</c> in the order they were supplied.</returns>
///
/// <exception cref="OperationCanceledException">Thrown if the provided cancellation token is canceled before the last computation starts, even if the <c>computations</c> ignore cancellation.</exception>
/// <example id="task-sequential-1">
/// <code lang="fsharp">
/// task {
Expand All @@ -598,7 +598,7 @@ module Task =
/// <param name="ct">A cancellation token to pass to each task factory.</param>
/// <param name="computations">A sequence of unit task start functions accepting a <see cref="T:System.Threading.CancellationToken"/>.</param>
/// <returns>A task that runs all inputs in sequence and returns <c>unit</c>.</returns>
///
/// <exception cref="OperationCanceledException">Thrown if the provided cancellation token is canceled before the last computation starts, even if the <c>computations</c> ignore cancellation.</exception>
/// <example id="task-sequentialdo-1">
/// <code lang="fsharp">
/// task {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,30 @@ module TaskModuleFunctionsTests =
Assert.Equal(1, maxConcurrent)
}

[<Theory; InlineData true; InlineData false>]
let ``Task.sequential honors cancellation even if the computations do not`` before : Task =
task {
use cts = new CancellationTokenSource()
let computations: (CancellationToken -> Task<int>) list =
if before then
cts.Cancel()
[ fun (_: CancellationToken) ->
task { return failwith "unexpected" }]
else
[ for i in 1..2 do
fun (_: CancellationToken) ->
task {
// whether cancellation during the last task is honored is up to the task in question
// the guarantee is that no further tasks will be started after cancellation is triggered
if i = 1 then
cts.Cancel()
return 42
}
]
let run (): Task = Task.sequential cts.Token computations
Assert.ThrowsAsync<OperationCanceledException>(run).Result |> ignore
}


[<Fact>]
let ``Task.sequentialDo runs all tasks in order and returns unit`` () : Task =
Expand Down Expand Up @@ -400,7 +424,30 @@ module TaskModuleFunctionsTests =
Assert.Equal(1, maxConcurrent)
}


[<Theory; InlineData true; InlineData false>]
let ``Task.sequentialDo honors cancellation even if the computations do not`` before : Task =
task {
use cts = new CancellationTokenSource()
let computations: (CancellationToken -> Task<unit>) list =
if before then
cts.Cancel()
[ fun (_: CancellationToken) ->
task { return failwith "unexpected" }]
else
[ for i in 1..2 do
fun (_: CancellationToken) ->
task {
// whether cancellation during the last task is honored is up to the task in question
// the guarantee is that no further tasks will be started after cancellation is triggered
if i = 1 then
cts.Cancel()
}
]
let run (): Task = Task.sequentialDo cts.Token computations
Assert.ThrowsAsync<OperationCanceledException>(run).Result |> ignore
}


[<Fact>]
let ``Task.parallelLimit runs all tasks and collects results`` () : Task =
task {
Expand Down
39 changes: 2 additions & 37 deletions vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1102,35 +1102,7 @@ module CancellableTasks =

/// Runs the given tasks concurrently, but caps concurrent work to maxDegreeOfParallelism.
let inline whenAllThrottled maxDegreeOfParallelism (tasks: CancellableTask<'a> seq) =
cancellableTask {
let! ct = getCancellationToken ()
let semaphore = new SemaphoreSlim(maxDegreeOfParallelism: int)

let started =
[|
for task in tasks do
backgroundTask {
do! semaphore.WaitAsync(ct)

try
return! start ct task
finally
semaphore.Release() |> ignore
}
|]

let allTask = Task.WhenAll started

allTask.ContinueWith(
(fun (_: Task<'a[]>) -> semaphore.Dispose()),
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default
)
|> ignore

return! allTask
}
fun ct -> Task.parallelLimit maxDegreeOfParallelism ct tasks

let inline whenAllTasks (tasks: CancellableTask seq) =
cancellableTask {
Expand All @@ -1140,14 +1112,7 @@ module CancellableTasks =
}

let inline sequential (tasks: CancellableTask<'a> seq) =
cancellableTask {
let! ct = getCancellationToken ()
let results = ResizeArray()
for task in tasks do
let! result = start ct task
results.Add(result)
return results
}
fun ct -> Task.sequential ct tasks

let inline ignore ([<InlineIfLambda>] ctask: CancellableTask<_>) = toUnit ctask

Expand Down
Loading