diff --git a/docs/release-notes/.VisualStudio/18.vNext.md b/docs/release-notes/.VisualStudio/18.vNext.md index cffa42edc9c..f23a2a2ce9c 100644 --- a/docs/release-notes/.VisualStudio/18.vNext.md +++ b/docs/release-notes/.VisualStudio/18.vNext.md @@ -5,6 +5,7 @@ ### Fixed +* Improve Find All References performance by throttling parallel typechecks. ([PR #20128](https://github.com/dotnet/fsharp/pull/20128)) * 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)) * Find All References for external DLL symbols now only searches projects that reference the specific assembly. ([Issue #10227](https://github.com/dotnet/fsharp/issues/10227), [PR #19252](https://github.com/dotnet/fsharp/pull/19252)) diff --git a/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs b/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs index 5056ddee461..7520395a084 100644 --- a/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs +++ b/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs @@ -1098,6 +1098,38 @@ module CancellableTasks = return! Task.WhenAll (tasks) } + /// 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 + } + let inline whenAllTasks (tasks: CancellableTask seq) = cancellableTask { let! ct = getCancellationToken () diff --git a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs index ef0929a1211..2406f3a6e32 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs @@ -1,4 +1,4 @@ -[] +[] module internal Microsoft.VisualStudio.FSharp.Editor.WorkspaceExtensions open System @@ -703,7 +703,8 @@ type Project with documents |> Seq.map (fun doc -> doc.FindFSharpReferencesAsync(symbol, projectSnapshot, (fun range -> onFound doc range), userOpName)) - |> CancellableTask.whenAll + // Throttle to avoid launching a typecheck per document in the project all at once. + |> CancellableTask.whenAllThrottled (max 1 Environment.ProcessorCount) else for doc in documents do do! doc.FindFSharpReferencesAsync(symbol, projectSnapshot, (onFound doc), userOpName)