Is your feature request related to a problem? Please describe.
CPU profiling of devenv.exe (Visual Studio Insiders + F# extension) shows ThreadPoolWorkQueue.Dispatch at 72 % total / 59 % self, driven by a continuous stream of small tasks from FSharp.Compiler.CheckDeclarations, FSharpProjectOptionsReactor, and async/cancellableTask state-machine overhead.
The root cause is not a single hot function but the volume of background tasks scheduled regardless of whether an F# document is actually active/visible. Roslyn's default BackgroundAnalysisScope for C# is VisibleFilesAndOpenFilesWithPreviouslyReportedDiagnostics, but F# analyzers currently run a full typecheck for every open tab on every crawler pass.
Describe the solution you'd like
Already implemented in this PR
A shared ActiveDocumentDetection module was extracted that uses SVsShellMonitorSelection / IVsMonitorSelection / IVsWindowFrame to determine whether a Roslyn Document is the currently focused editor tab. The module is now applied to the four heaviest analyzers/services:
| Component |
File |
Fix applied |
UnusedOpensDiagnosticAnalyzer |
Diagnostics/UnusedOpensDiagnosticAnalyzer.fs |
active-document gate (was already in place, refactored to shared module) |
UnusedDeclarationsAnalyzer |
Diagnostics/UnusedDeclarationsAnalyzer.fs |
active-document gate added |
SimplifyNameDiagnosticAnalyzer |
Diagnostics/SimplifyNameDiagnosticAnalyzer.fs |
active-document gate added |
FSharpInlayHintsService |
Hints/FSharpInlayHintsService.fs |
active-document gate added |
The shared helper (ActiveDocumentDetection.isActiveDocument) falls back to true (= don't suppress) when the active document cannot be determined, so analysis is never silently lost.
Remaining work (tracked in docs/ide/background-activity-minimization-plan.md)
| # |
Source |
Estimated impact |
Risk |
| 4 |
ClassificationService.AddSemanticClassificationsAsync — cache miss triggers full typecheck for every open tab |
Medium |
Low-medium |
| 5 |
createPEReference / Compilation.Emit — re-emits metadata for every C# referenced project on each workspace event |
High |
Medium |
| 6 |
hasDependentVersionChanged — synchronous .Result blocks the reactor MailboxProcessor thread |
Medium |
Low |
| 7 |
FSharpProjectOptionsReactor — FIFO queue starves active-document requests |
Medium |
Medium |
| 8 |
Script updateProjectOptions fires on every caret move |
Low-medium |
Low |
| 9 |
DocumentDiagnosticAnalyzer semantic path — no text-version short-circuit |
Medium |
Low |
| 10 |
SymbolHelpers.findReferencedSymbolsAsync — typechecks all documents in solution |
Low (on-demand) |
Low |
Full root-cause descriptions and proposed fixes are in docs/ide/background-activity-minimization-plan.md.
Describe alternatives you've considered
- Lowering
BackgroundAnalysisScope globally via IGlobalOptionService — blocked by internal Roslyn API; proposed as a future Roslyn API extension in docs/ide/api-designs/Active-Document-Background-Analysis.md.
- Using
IDocumentTrackingService from Roslyn — not exposed via ExternalAccess; same future-API path.
SemaphoreSlim throttling alone — already in place for SimplifyName but does not eliminate unnecessary work, just caps it.
Additional context
- Profile was collected on Visual Studio 18.9 Insiders (
devenv.exe) with the /rootsuffix RoslynDev hive.
- The immediate symptom is sluggish UI and continuous CPU consumption even when the user is not typing or switching tabs.
- The fix is fully backward-compatible: non-active documents receive fresh diagnostics/hints as soon as the user switches to them.
Is your feature request related to a problem? Please describe.
CPU profiling of
devenv.exe(Visual Studio Insiders + F# extension) showsThreadPoolWorkQueue.Dispatchat 72 % total / 59 % self, driven by a continuous stream of small tasks fromFSharp.Compiler.CheckDeclarations,FSharpProjectOptionsReactor, and async/cancellableTaskstate-machine overhead.The root cause is not a single hot function but the volume of background tasks scheduled regardless of whether an F# document is actually active/visible. Roslyn's default
BackgroundAnalysisScopefor C# isVisibleFilesAndOpenFilesWithPreviouslyReportedDiagnostics, but F# analyzers currently run a full typecheck for every open tab on every crawler pass.Describe the solution you'd like
Already implemented in this PR
A shared
ActiveDocumentDetectionmodule was extracted that usesSVsShellMonitorSelection/IVsMonitorSelection/IVsWindowFrameto determine whether a RoslynDocumentis the currently focused editor tab. The module is now applied to the four heaviest analyzers/services:UnusedOpensDiagnosticAnalyzerDiagnostics/UnusedOpensDiagnosticAnalyzer.fsUnusedDeclarationsAnalyzerDiagnostics/UnusedDeclarationsAnalyzer.fsSimplifyNameDiagnosticAnalyzerDiagnostics/SimplifyNameDiagnosticAnalyzer.fsFSharpInlayHintsServiceHints/FSharpInlayHintsService.fsThe shared helper (
ActiveDocumentDetection.isActiveDocument) falls back totrue(= don't suppress) when the active document cannot be determined, so analysis is never silently lost.Remaining work (tracked in
docs/ide/background-activity-minimization-plan.md)ClassificationService.AddSemanticClassificationsAsync— cache miss triggers full typecheck for every open tabcreatePEReference/Compilation.Emit— re-emits metadata for every C# referenced project on each workspace eventhasDependentVersionChanged— synchronous.Resultblocks the reactorMailboxProcessorthreadFSharpProjectOptionsReactor— FIFO queue starves active-document requestsupdateProjectOptionsfires on every caret moveDocumentDiagnosticAnalyzersemantic path — no text-version short-circuitSymbolHelpers.findReferencedSymbolsAsync— typechecks all documents in solutionFull root-cause descriptions and proposed fixes are in
docs/ide/background-activity-minimization-plan.md.Describe alternatives you've considered
BackgroundAnalysisScopeglobally viaIGlobalOptionService— blocked by internal Roslyn API; proposed as a future Roslyn API extension indocs/ide/api-designs/Active-Document-Background-Analysis.md.IDocumentTrackingServicefrom Roslyn — not exposed via ExternalAccess; same future-API path.SemaphoreSlimthrottling alone — already in place forSimplifyNamebut does not eliminate unnecessary work, just caps it.Additional context
devenv.exe) with the/rootsuffix RoslynDevhive.