Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Runspace Dispose in Powershell (GUI specific)

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!

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
like image 366
Carlton2001 Avatar asked Aug 11 '18 12:08

Carlton2001


1 Answers

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()
like image 172
Mathias R. Jessen Avatar answered Oct 18 '22 02:10

Mathias R. Jessen