I'm an active reader of StackOverflow as it usually helps me resolve all my issues.
But for my first question ever I'll ask your help about runspaces in Powershell, specifically for memory management.
I created some PS apps with GUI and now I use runspaces to avoid hang of the GUI when big operations are running. My problem is that I can't manage to dispose my runspaces when they are finished.
Regarding the code below, it's a simple script to try to understand how I can dispose of my runspaces. I tried to incorporate a unique runspace to monitor and dispose the app runspaces, I inspired myself from a proxb's script (huge fan btw) but it doesn't seems to work.
Everytime I execute the runspace I gain 10Mb of memory, huge leak!
Can you help me to resolve this problem please ?
[xml]$xaml = @'
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Runspace" FontFamily="Calibri" Height="400" Width="400" FontSize="14">
<Grid Margin="10,10,10,10">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="10"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox x:Name="TB_Results" Grid.Row="0" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"/>
<Button x:Name="BT_Run" Grid.Row="2" Content="Run" Height="30"/>
</Grid>
</Window>
'@
# Inits
Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName PresentationCore
Add-Type -AssemblyName WindowsBase
$gui = [hashtable]::Synchronized(@{})
$reader = (New-Object Xml.XmlNodeReader $xaml)
$gui.Window = [Windows.Markup.XamlReader]::Load($reader)
$xaml.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]") | ForEach-Object { $gui.Add($_.Name,$gui.Window.FindName($_.Name)) }
$Script:Jobs = [system.collections.arraylist]::Synchronized((New-Object System.Collections.ArrayList))
$Script:JobCleanup = [hashtable]::Synchronized(@{ Host = $host })
# Test function
function RunFunc {
$newRunspace = [runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "ReuseThread"
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("gui",$gui)
$Code = {
$memupdate = "Memory: $($(Get-Process -Id $PID).PrivateMemorySize64 / 1mb) mb"
$gui.Window.Dispatcher.invoke("Normal",[action]{
$gui.TB_Results.Text = "$memupdate`r`n" + $gui.TB_Results.Text
})
}
$Powershell = [powershell]::Create().AddScript($Code)
$Powershell.Runspace = $newRunspace
[void]$Script:Jobs.Add((
[PSCustomObject]@{
PowerShell = $PowerShell
Runspace = $PowerShell.BeginInvoke()
}
))
}
# Background Runspace to clean up jobs
$newRunspace =[runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "ReuseThread"
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("jobs",$Script:Jobs)
$Code = {
$JobCleanup = $true
do {
foreach($runspace in $jobs) {
if ($runspace.Runspace.isCompleted) {
$runspace.powershell.EndInvoke($runspace.Runspace) | Out-Null
$runspace.powershell.dispose()
$runspace.Runspace = $null
$runspace.powershell = $null
}
}
$temphash = $jobs.clone()
$temphash | Where-Object { $_.runspace -eq $Null } | ForEach-Object { $jobs.remove($_) }
Start-Sleep -Seconds 1
} while ($JobCleanup)
}
$Powershell = [powershell]::Create().AddScript($Code)
$Powershell.Runspace = $newRunspace
$PowerShell.BeginInvoke()
# gui events
$gui.BT_Run.add_Click({ RunFunc })
$gui.Window.ShowDialog() | Out-Null
When you call $runspace.PowerShell.Dispose()
, the PowerShell
instance is disposed, but the runspace that it's tied to is not automatically disposed, you'll need to do that first yourself in the cleanup task:
$runspace.powershell.EndInvoke($runspace.Runspace) | Out-Null
$runspace.powershell.Runspace.Dispose() # remember to clean up the runspace!
$runspace.powershell.dispose()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With