Hi,
The problem: Adding Nodes to TreeView is very slow if I’m using PowerShell code that invokes the control methods. The same code used via custom c# types is very fast.
Why launching the same tasks from PS is so slow? Just run the below code, click one of the buttons to see a huge difference. Does anyone have an idea how it could be improved without relying on custom c# code?
Code: Untitled (a4py9egg) - PasteCode.io
using namespace System.Windows.Forms
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$form1 = New-Object 'System.Windows.Forms.Form'
$buttonRunPS = New-Object 'System.Windows.Forms.Button'
$buttonRunC = New-Object 'System.Windows.Forms.Button'
$richtextbox2 = New-Object 'System.Windows.Forms.RichTextBox'
$richtextbox1 = New-Object 'System.Windows.Forms.RichTextBox'
$treeview1 = New-Object 'System.Windows.Forms.TreeView'
$InitialFormWindowState = New-Object 'System.Windows.Forms.FormWindowState'
function Start-RunspaceTask {
[CmdletBinding()]
Param (
[Parameter(Mandatory, Position = 0)][ScriptBlock]$ScriptBlock,
[Parameter(Mandatory, Position = 1)][PSObject[]]$ProxyVars
)
$Runspace = [RunspaceFactory]::CreateRunspace($InitialSessionState)
$Runspace.ApartmentState = 'STA'
$Runspace.ThreadOptions = 'ReuseThread'
$Runspace.Open()
ForEach ($Var in $ProxyVars) { $Runspace.SessionStateProxy.SetVariable($Var.VariableName, $Var.VariableValue) }
$Thread = [PowerShell]::Create()
$Thread.AddScript($ScriptBlock, $True) | Out-Null
$Thread.Runspace = $Runspace
[Void]$jobList.Add([PSObject]@{ PowerShell = $Thread; Runspace = $Thread.BeginInvoke() })
}
#System.Collections.Concurrent is thread-safe
$global:SyncHash = [System.Collections.Concurrent.ConcurrentDictionary[string, object]]::new()
$global:jobList = [System.Collections.Concurrent.ConcurrentBag[object]]::new()
$initialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
$SyncHash.form1 = $form1
$SyncHash.treeview1 = $treeview1
$SyncHash.richtextbox1 = $richtextbox1
$SyncHash.richtextbox2 = $richtextbox2
$buttonRunC_Click = {
#region Custom Types
Add-Type -ReferencedAssemblies System.Windows.Forms, System.ComponentModel, System.ComponentModel.Primitives, System.Threading.Tasks -TypeDefinition @'
using System.Threading.Tasks;
using System.Windows.Forms;
public class TreeViewHelper
{
public static void AddNode(TreeView TreeView, TreeNode TreeNode)
{
TreeView.Invoke((MethodInvoker)(() => TreeView.Nodes.Add(TreeNode)));
}
public static async void AddNodeAsync(TreeView TreeView, string Name)
{
await Task.Run(() =>
{
TreeView.Invoke((MethodInvoker)(() => TreeView.Nodes.Add(Name)));
});
}
}
'@
Add-Type -ReferencedAssemblies System.Windows.Forms, System.ComponentModel, System.ComponentModel.Primitives, System.Threading.Tasks -TypeDefinition @'
using System.Threading.Tasks;
using System.Windows.Forms;
public class RichTextBoxHelper
{
public static void AddText(RichTextBox RichTextBoxControl, string Text)
{
RichTextBoxControl.Invoke((MethodInvoker)(() => RichTextBoxControl.AppendText(Text)));
}
public static async void AddTextAsync(RichTextBox RichTextBoxControl, string Text)
{
await Task.Run(() =>
{
RichTextBoxControl.Invoke((MethodInvoker)(() => RichTextBoxControl.AppendText(Text)));
});
}
}
'@
#endregion
$ScriptBlock = {
$treeview1.Nodes.Clear() | Out-Null
Get-ChildItem Function: | Where-Object { $_.Name -NotLike "*:*" } | Select-Object -ExpandProperty Name | ForEach-Object {
$Definition = Get-Content "function:$_" -ErrorAction Stop
$SessionStateFunction = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList "$_", $Definition
$InitialSessionState.Commands.Add($SessionStateFunction)
}
$TopLevelDir = 'C:\Windows\System32'
foreach ($FilePath in [System.Io.Directory]::EnumerateFiles($TopLevelDir, "*.dll", [System.IO.SearchOption]::AllDirectories)) {
$counter = 0
$streamReader = [System.IO.StreamReader]::new($FilePath, [System.Text.Encoding]::Default)
while ($null -ne ($line = $streamReader.ReadLine()) -and $counter -le 100) {
#simulate read file work
$counter++
}
$streamReader.Close()
$streamReader.Dispose()
[RichTextBoxHelper]::AddText($SyncHash.richtextbox2, $FilePath)
[TreeViewHelper]::AddNode($SyncHash.treeview1, $FilePath) # works from Runspace
}
}
Start-RunspaceTask $ScriptBlock -ProxyVars @([PSObject]@{ VariableName = 'SyncHash'; VariableValue = $SyncHash })
#$ScriptBlock.invoke() #replace Start-RunspaceTask with this line if you want to debug code
}
$buttonRunPS_Click = {
$ScriptBlock = {
$treeview1.Nodes.Clear() | Out-Null
Get-ChildItem Function: | Where-Object { $_.Name -NotLike "*:*" } | Select-Object -ExpandProperty Name | ForEach-Object {
$Definition = Get-Content "function:$_" -ErrorAction Stop
$SessionStateFunction = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList "$_", $Definition
$InitialSessionState.Commands.Add($SessionStateFunction)
}
$TopLevelDir = 'C:\Windows\System32'
foreach ($FilePath in [System.Io.Directory]::EnumerateFiles($TopLevelDir, "*.dll", [System.IO.SearchOption]::AllDirectories)) {
$counter = 0
$streamReader = [System.IO.StreamReader]::new($FilePath, [System.Text.Encoding]::Default)
while ($null -ne ($line = $streamReader.ReadLine()) -and $counter -le 100) {
#simulate read file work
$counter++
}
$streamReader.Close()
$streamReader.Dispose()
$SyncHash.richtextbox2.Invoke([System.Windows.Forms.MethodInvoker]{ $SyncHash.richtextbox2.AppendText($FilePath + "`n") }) # works from Runspace but very slow
$SyncHash.treeview1.Invoke([System.Windows.Forms.MethodInvoker]{ $SyncHash.treeview1.Nodes.Add($FilePath) }) # works from Runspace but very slow
}
}
Start-RunspaceTask $ScriptBlock -ProxyVars @([PSObject]@{ VariableName = 'SyncHash'; VariableValue = $SyncHash })
#$ScriptBlock.invoke() #replace Start-RunspaceTask with this line if you nwat to debug
}
#region Form Code
$form1.SuspendLayout()
$form1.Controls.Add($buttonRunPS)
$form1.Controls.Add($buttonRunC)
$form1.Controls.Add($richtextbox2)
$form1.Controls.Add($richtextbox1)
$form1.Controls.Add($treeview1)
$form1.AutoScaleDimensions = New-Object System.Drawing.SizeF(6, 13)
$form1.AutoScaleMode = 'Font'
$form1.ClientSize = New-Object System.Drawing.Size(548, 647)
$form1.Name = 'form1'
$form1.Text = 'Form'
$buttonRunPS.Anchor = 'Bottom, Right'
$buttonRunPS.Location = New-Object System.Drawing.Point(432, 612)
$buttonRunPS.Name = 'buttonRunPS'
$buttonRunPS.Size = New-Object System.Drawing.Size(104, 23)
$buttonRunPS.TabIndex = 4
$buttonRunPS.Text = 'Run PS'
$buttonRunPS.UseVisualStyleBackColor = $True
$buttonRunPS.add_Click($buttonRunPS_Click)
$buttonRunC.Anchor = 'Bottom, Left'
$buttonRunC.Location = New-Object System.Drawing.Point(304, 612)
$buttonRunC.Name = 'buttonRunC'
$buttonRunC.Size = New-Object System.Drawing.Size(122, 23)
$buttonRunC.TabIndex = 3
$buttonRunC.Text = 'Run C#'
$buttonRunC.UseVisualStyleBackColor = $True
$buttonRunC.add_Click($buttonRunC_Click)
$richtextbox2.Anchor = 'Top, Bottom, Left, Right'
$richtextbox2.Location = New-Object System.Drawing.Point(304, 295)
$richtextbox2.Name = 'richtextbox2'
$richtextbox2.Size = New-Object System.Drawing.Size(232, 311)
$richtextbox2.TabIndex = 2
$richtextbox2.Text = ''
$richtextbox1.Anchor = 'Top, Left, Right'
$richtextbox1.Location = New-Object System.Drawing.Point(303, 12)
$richtextbox1.Name = 'richtextbox1'
$richtextbox1.Size = New-Object System.Drawing.Size(233, 276)
$richtextbox1.TabIndex = 1
$richtextbox1.Text = ''
$treeview1.Anchor = 'Top, Bottom, Left'
$treeview1.Location = New-Object System.Drawing.Point(12, 12)
$treeview1.Name = 'treeview1'
$treeview1.Size = New-Object System.Drawing.Size(285, 623)
$treeview1.TabIndex = 0
$form1.ResumeLayout()
#endregion
$form1.ShowDialog()