Speed up runspace and tab initialization (#4793)

* Add startup performance tracing

* Lazy initialize non-default tabs

* Render install app entries incrementally

* Defer and cache toggle status checks

* Clean up runspace invocation ownership

* Defer GUI runspace pool startup

* Defer status taskbar asset rendering

* Record final speed verification

* Fix deferred install render timer callback

* Add dispatcher smoke coverage for install rendering

* Replace install render timer with dispatcher callbacks

* Gate performance tracing behind compile switch

* Clean up analyzer warnings in speed changes

* Speed up runspace and tab initialization
This commit is contained in:
Chris Titus
2026-07-01 23:34:59 -05:00
committed by GitHub
parent 916dc761ba
commit 93dc23dd66
31 changed files with 1114 additions and 133 deletions
@@ -0,0 +1,18 @@
function Close-WinUtilRunspacePool {
if ($null -eq $sync -or -not $sync.ContainsKey("runspace") -or $null -eq $sync.runspace) {
return
}
try {
if ($sync.runspace.RunspacePoolStateInfo.State -notin @(
[System.Management.Automation.Runspaces.RunspacePoolState]::Closed,
[System.Management.Automation.Runspaces.RunspacePoolState]::Closing,
[System.Management.Automation.Runspaces.RunspacePoolState]::Broken
)) {
$sync.runspace.Close()
}
} finally {
$sync.runspace.Dispose()
$sync.Remove("runspace")
}
}
+15 -5
View File
@@ -2,29 +2,39 @@ Function Get-WinUtilToggleStatus ($ToggleSwitch) {
$ToggleSwitchReg = $sync.configs.tweaks.$ToggleSwitch.registry
if ($null -eq $sync.ToggleStatusCache) {
$sync.ToggleStatusCache = @{}
}
if ($sync.ToggleStatusCache.ContainsKey($ToggleSwitch)) {
return [bool]$sync.ToggleStatusCache[$ToggleSwitch]
}
if (-not (Get-PSDrive -Name HKU -ErrorAction SilentlyContinue)) {
New-PSDrive -PSProvider Registry -Name HKU -Root HKEY_USERS | Out-Null
}
foreach ($regentry in $ToggleSwitchReg) {
if (-not (Test-Path $regentry.Path)) {
New-Item -Path $regentry.Path -Force | Out-Null
if (Test-Path $regentry.Path) {
$regstate = (Get-ItemProperty -Path $regentry.Path).$($regentry.Name)
} else {
$regstate = $null
}
$regstate = (Get-ItemProperty -Path $regentry.Path).$($regentry.Name)
if ($null -eq $regstate) {
switch ($regentry.DefaultState) {
switch ([string]$regentry.DefaultState) {
"true" { $regstate = $regentry.Value }
"false" { $regstate = $regentry.OriginalValue }
}
}
if ($regstate -ne $regentry.Value) {
$sync.ToggleStatusCache[$ToggleSwitch] = $false
return $false
}
}
$sync.ToggleStatusCache[$ToggleSwitch] = $true
return $true
}
@@ -13,11 +13,13 @@ function Initialize-InstallAppEntry {
$appKey
)
$app = $sync.configs.applicationsHashtable.$appKey
# Create the outer Border for the application type
$border = New-Object Windows.Controls.Border
$border.Style = $sync.Form.Resources.AppEntryBorderStyle
$border.Tag = $appKey
$border.ToolTip = $Apps.$appKey.description
$border.ToolTip = $app.description
$border.Add_MouseLeftButtonUp({
$childCheckbox = ($this.Child | Where-Object {$_.Template.TargetType -eq [System.Windows.Controls.Checkbox]})[0]
$childCheckBox.isChecked = -not $childCheckbox.IsChecked
@@ -61,10 +63,10 @@ function Initialize-InstallAppEntry {
# Create the TextBlock for the application name
$appName = New-Object Windows.Controls.TextBlock
$appName.Style = $sync.Form.Resources.AppEntryNameStyle
$appName.Text = $Apps.$appKey.content
$appName.Text = $app.content
# Add FOSS label after the name if FOSS
if ($Apps.$appKey.foss -eq $true) {
if ($app.foss -eq $true) {
$fossRun = [System.Windows.Documents.Run]::new(" $([char]0x25CF)")
$fossRun.Foreground = [Windows.Media.SolidColorBrush]::new([Windows.Media.Color]::FromRgb(110, 255, 114))
$fossRun.FontSize = 11.5
@@ -74,10 +76,13 @@ function Initialize-InstallAppEntry {
$checkBox.Content = $appName
# Add accessibility properties to make the elements screen reader friendly
$checkBox.SetValue([Windows.Automation.AutomationProperties]::NameProperty, $Apps.$appKey.content)
$border.SetValue([Windows.Automation.AutomationProperties]::NameProperty, $Apps.$appKey.content)
$checkBox.SetValue([Windows.Automation.AutomationProperties]::NameProperty, $app.content)
$border.SetValue([Windows.Automation.AutomationProperties]::NameProperty, $app.content)
$border.Child = $checkBox
if ($sync.selectedApps -contains $appKey) {
$checkBox.IsChecked = $true
}
# Add the border to the corresponding Category
$TargetElement.Children.Add($border) | Out-Null
return $checkbox
@@ -16,7 +16,7 @@ function Initialize-InstallCategoryAppList {
$Apps
)
# Pre-group apps by category
# Pre-group apps by category before creating WPF controls.
$appsByCategory = @{}
foreach ($appKey in $Apps.Keys) {
$category = $Apps.$appKey.Category
@@ -25,6 +25,8 @@ function Initialize-InstallCategoryAppList {
}
$appsByCategory[$category] += $appKey
}
$sync.InstallAppRenderQueue = [System.Collections.Queue]::new()
foreach ($category in $($appsByCategory.Keys | Sort-Object)) {
# Create a container for category label + apps
$categoryContainer = New-Object Windows.Controls.StackPanel
@@ -52,10 +54,10 @@ function Initialize-InstallCategoryAppList {
# Add click handler to toggle category visibility
$toggleButton.Add_MouseLeftButtonUp({
param($sender, $e)
param($categoryToggle)
# Find the parent StackPanel (categoryContainer)
$categoryContainer = $sender.Parent
$categoryContainer = $categoryToggle.Parent
if ($categoryContainer -and $categoryContainer.Children.Count -ge 2) {
# The WrapPanel is the second child
$wrapPanel = $categoryContainer.Children[1]
@@ -64,11 +66,11 @@ function Initialize-InstallCategoryAppList {
if ($wrapPanel.Visibility -eq [Windows.Visibility]::Visible) {
$wrapPanel.Visibility = [Windows.Visibility]::Collapsed
# Change - to +
$sender.Content = $sender.Content -replace "^- ", "+ "
$categoryToggle.Content = $categoryToggle.Content -replace "^- ", "+ "
} else {
$wrapPanel.Visibility = [Windows.Visibility]::Visible
# Change + to -
$sender.Content = $sender.Content -replace "^\+ ", "- "
$categoryToggle.Content = $categoryToggle.Content -replace "^\+ ", "- "
}
}
})
@@ -89,9 +91,12 @@ function Initialize-InstallCategoryAppList {
# Add the entire category container to the target element
$null = $TargetElement.Items.Add($categoryContainer)
# Add apps to the wrap panel
$appsByCategory[$category] | Sort-Object | ForEach-Object {
$sync.$_ = $(Initialize-InstallAppEntry -TargetElement $wrapPanel -AppKey $_)
}
$sync.InstallAppRenderQueue.Enqueue([pscustomobject]@{
Category = $category
TargetElement = $wrapPanel
AppKeys = @($appsByCategory[$category] | Sort-Object)
})
}
Start-WinUtilInstallAppRendering
}
@@ -0,0 +1,39 @@
function Initialize-WinUtilRunspacePool {
if ($sync.runspace -and $sync.runspace.RunspacePoolStateInfo.State -eq [System.Management.Automation.Runspaces.RunspacePoolState]::Opened) {
return $sync.runspace
}
if ($sync.runspace) {
Close-WinUtilRunspacePool
}
# Set the maximum number of threads for the RunspacePool to the number of threads on the machine.
$maxthreads = [Math]::Max([int]$env:NUMBER_OF_PROCESSORS, 1)
# Create a new session state for parsing variables into our runspace.
$hashVars = New-Object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList 'sync', $sync, $null
$offlineVar = New-Object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList 'PARAM_OFFLINE', $PARAM_OFFLINE, $null
$initialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
$initialSessionState.Variables.Add($hashVars)
$initialSessionState.Variables.Add($offlineVar)
# Get every WinUtil/WPF function and add it to the session state.
$functions = Get-ChildItem function:\ | Where-Object { $_.Name -imatch 'winutil|WPF' }
foreach ($function in $functions) {
$functionDefinition = Get-Content function:\$($function.Name)
$functionEntry = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $function.Name, $functionDefinition
$initialSessionState.Commands.Add($functionEntry)
}
$sync.runspace = [runspacefactory]::CreateRunspacePool(
1, # Minimum thread count
$maxthreads, # Maximum thread count
$initialSessionState, # Initial session state
$Host # Machine to create runspaces on
)
$sync.runspace.Open()
Write-WinUtilPerformanceCheckpoint -Name "Runspace pool initialized"
return $sync.runspace
}
@@ -0,0 +1,45 @@
function Initialize-WinUtilTabContent {
param(
[Parameter(Mandatory = $true)]
[string]$TabName
)
if ($null -eq $sync.InitializedTabs) {
$sync.InitializedTabs = @{}
}
if ($sync.InitializedTabs[$TabName]) {
return
}
switch ($TabName) {
"Install" {
Invoke-WPFUIElements -configVariable $sync.configs.appnavigation -targetGridName "appscategory" -columncount 1
Initialize-WPFUI -targetGridName "appscategory"
Write-WinUtilPerformanceCheckpoint -Name "App navigation UI created"
Initialize-WPFUI -targetGridName "appspanel"
Write-WinUtilPerformanceCheckpoint -Name "Install UI created"
}
"Tweaks" {
Invoke-WPFUIElements -configVariable $sync.configs.tweaks -targetGridName "tweakspanel" -columncount 2
Write-WinUtilPerformanceCheckpoint -Name "Tweaks UI created"
}
"Config" {
Invoke-WPFUIElements -configVariable $sync.configs.feature -targetGridName "featurespanel" -columncount 2
Write-WinUtilPerformanceCheckpoint -Name "Features UI created"
}
"AppX" {
Invoke-WPFUIElements -configVariable $sync.configs.appx -targetGridName "appxpanel" -columncount 2
Write-WinUtilPerformanceCheckpoint -Name "AppX UI created"
}
"Win11 Creator" {
if ($sync.Form -and $sync.Form.Dispatcher) {
$sync.Form.Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{ Invoke-WinUtilISOCheckExistingWork }) | Out-Null
}
Write-WinUtilPerformanceCheckpoint -Name "Win11 ISO tab initialized"
}
}
$sync.InitializedTabs[$TabName] = $true
}
@@ -0,0 +1,21 @@
function Initialize-WinUtilTaskbarOverlayAssets {
param(
[bool]$IncludeLogo = $true,
[bool]$IncludeStatusAssets = $true
)
if ($IncludeLogo -and -not $sync["logorender"]) {
$sync["logorender"] = (Invoke-WinUtilAssets -Type "Logo" -Size 90 -Render)
Write-WinUtilPerformanceCheckpoint -Name "Taskbar logo asset rendered"
}
if ($IncludeStatusAssets -and -not $sync["checkmarkrender"]) {
$sync["checkmarkrender"] = (Invoke-WinUtilAssets -Type "checkmark" -Size 512 -Render)
Write-WinUtilPerformanceCheckpoint -Name "Taskbar checkmark asset rendered"
}
if ($IncludeStatusAssets -and -not $sync["warningrender"]) {
$sync["warningrender"] = (Invoke-WinUtilAssets -Type "warning" -Size 512 -Render)
Write-WinUtilPerformanceCheckpoint -Name "Taskbar warning asset rendered"
}
}
@@ -5,6 +5,17 @@ function Invoke-WinUtilAssets {
[switch]$render
)
if ($render -and $null -ne $sync) {
if ($null -eq $sync.RenderedAssetCache) {
$sync.RenderedAssetCache = @{}
}
$cacheKey = "$(([string]$type).ToLowerInvariant())|$Size"
if ($sync.RenderedAssetCache.ContainsKey($cacheKey)) {
return $sync.RenderedAssetCache[$cacheKey]
}
}
# Create the Viewbox and set its size
$LogoViewbox = New-Object Windows.Controls.Viewbox
$LogoViewbox.Width = $Size
@@ -191,6 +202,13 @@ C 21.36,47.14 28.67,50.71 30.01,52.63
$bitmapImage.StreamSource = $imageStream
$bitmapImage.CacheOption = [Windows.Media.Imaging.BitmapCacheOption]::OnLoad
$bitmapImage.EndInit()
if ($bitmapImage.CanFreeze) {
$bitmapImage.Freeze()
}
if ($null -ne $sync -and $sync.ContainsKey("RenderedAssetCache")) {
$sync.RenderedAssetCache[$cacheKey] = $bitmapImage
}
return $bitmapImage
} else {
@@ -61,12 +61,21 @@ function Set-WinUtilTaskbaritem {
if ($overlay) {
switch ($overlay) {
'logo' {
if (-not $sync["logorender"]) {
Initialize-WinUtilTaskbarOverlayAssets -IncludeLogo $true -IncludeStatusAssets $false
}
$sync["Form"].taskbarItemInfo.Overlay = $sync["logorender"]
}
'checkmark' {
if (-not $sync["checkmarkrender"]) {
Initialize-WinUtilTaskbarOverlayAssets -IncludeLogo $false -IncludeStatusAssets $true
}
$sync["Form"].taskbarItemInfo.Overlay = $sync["checkmarkrender"]
}
'warning' {
if (-not $sync["warningrender"]) {
Initialize-WinUtilTaskbarOverlayAssets -IncludeLogo $false -IncludeStatusAssets $true
}
$sync["Form"].taskbarItemInfo.Overlay = $sync["warningrender"]
}
'None' {
@@ -0,0 +1,59 @@
function Invoke-WinUtilInstallAppRenderBatch {
param(
[Parameter(Mandatory = $true)]
$CategoryBatch
)
foreach ($appKey in $CategoryBatch.AppKeys) {
$sync.$appKey = Initialize-InstallAppEntry -TargetElement $CategoryBatch.TargetElement -AppKey $appKey
}
if ($sync.currentTab -eq "Install" -and $sync.SearchBar -and -not [string]::IsNullOrWhiteSpace($sync.SearchBar.Text)) {
Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text
}
}
function Complete-WinUtilInstallAppRendering {
$sync.InstallAppEntriesRendered = $true
Write-WinUtilPerformanceCheckpoint -Name "Install app entries rendered"
}
function Invoke-WinUtilInstallAppRenderNextBatch {
if ($sync.InstallAppRenderQueue.Count -gt 0) {
$categoryBatch = $sync.InstallAppRenderQueue.Dequeue()
Invoke-WinUtilInstallAppRenderBatch -CategoryBatch $categoryBatch
}
if ($sync.InstallAppRenderQueue.Count -gt 0) {
$sync.Form.Dispatcher.BeginInvoke(
[System.Windows.Threading.DispatcherPriority]::Background,
[action]{ Invoke-WinUtilInstallAppRenderNextBatch }
) | Out-Null
return
}
Complete-WinUtilInstallAppRendering
}
function Start-WinUtilInstallAppRendering {
if ($null -eq $sync.InstallAppRenderQueue) {
return
}
$sync.InstallAppEntriesRendered = $false
if ($sync.Form -and $sync.Form.Dispatcher) {
$sync.Form.Dispatcher.BeginInvoke(
[System.Windows.Threading.DispatcherPriority]::Background,
[action]{ Invoke-WinUtilInstallAppRenderNextBatch }
) | Out-Null
return
}
while ($sync.InstallAppRenderQueue.Count -gt 0) {
$categoryBatch = $sync.InstallAppRenderQueue.Dequeue()
Invoke-WinUtilInstallAppRenderBatch -CategoryBatch $categoryBatch
}
Complete-WinUtilInstallAppRendering
}