在单独的运行空间中管道 ListView 项目
Piping ListView items in separate runspace
我有一个用 PowerShell 编写的表单(因为它在数百台服务器上使用 PS 到 运行 命令)但是这一点让我困惑了一段时间:
$listview.Invoke([System.Action[String]]{
Param
(
[String]$data
)
write-warning "1"
$LVitem = ($LVresults.Items | Where { $_.Name -eq "Test Account" })
write-warning "2"
$LVitem.Tag.Output += $data + "`n"
}, "Testing")
在单独的 运行 空间中,运行 向特定服务器发出调用命令并将输出通过管道传输到此代码块。
现在,如果我 运行 在主 运行 空间(创建表单的地方)中的 Where 语句,它可以很好地工作。在单独的 运行 空间中,它会锁定表单。显示警告1,不显示警告2。
管道到 Foreach 语句有同样的问题,但我可以使用:
Foreach ($item in $listview.Items) {
if ($item.Name -eq "Test Account") { $LVitem = $item }
}
谁能解释一下?我没有对 ListView 或其项目做任何花哨的事情,它只是看起来 ListView 不喜欢它的项目被输送到另一个 运行 空间。
这是 PowerShell 事件系统的问题。当事件处理程序生成带有等待完成选项的事件时,它会死锁。
Register-EngineEvent -SourceIdentifier MyEvent1 -Action {Write-Host 'Does not get called.'}
Register-EngineEvent -SourceIdentifier MyEvent2 -Action {$ExecutionContext.Events.GenerateEvent('MyEvent1',$null,$null,$null,$true,$true)}
New-Event -SourceIdentifier MyEvent2
那么,您的设置与事件有什么关系?
脚本块文字会在创建时记住当前会话状态(包括 Runspace
)。如果在脚本块调用时间当前线程 ([Runspace]::DefaultRunspace
) 的当前 Runspace
不同或忙于处理不同线程中的某些内容,则脚本块通过生成原始 Runspace
的事件来调用并等待完成选项。
另一个有趣的事情是:如果目标Runspace
没有在250毫秒内开始处理事件,那么PowerShell会在等待事件完成的线程中开始事件处理。
在您的代码中有两个嵌套的脚本块调用:
- 脚本块作为委托传递给
$listview.Invoke
方法。
- 传递给
Where-Object
cmdlet 的脚本块。
据我所知,您的代码具有三种可能的结果:
脚本块的原始 Runspace
是免费的,因此脚本块在不同的线程中执行(不是 UI 线程)。
$PowerShell=[PowerShell]::Create()
$PowerShell.Runspace=[RunspaceFactory]::CreateRunspace($Host)
$PowerShell.Runspace.Open()
$Stopwatch=[Diagnostics.Stopwatch]::new()
$PowerShell.Runspace.SessionStateProxy.PSVariable.Set('Stopwatch',$Stopwatch)
$ScriptBlock=$PowerShell.AddScript{{
Write-Host "$($Stopwatch.ElapsedMilliseconds): $([Threading.Thread]::CurrentThread.ManagedThreadId)"
{Write-Host OK}.Invoke()
}}.Invoke()
$PowerShell.Commands.Clear()
& {
$Stopwatch.Start()
Write-Host "$($Stopwatch.ElapsedMilliseconds): $([Threading.Thread]::CurrentThread.ManagedThreadId)"
$ScriptBlock.Invoke()
}
脚本块的原始 Runspace
很忙,因此脚本块在当前线程中执行并在嵌套脚本块调用时出现死锁。
$PowerShell=[PowerShell]::Create()
$PowerShell.Runspace=[RunspaceFactory]::CreateRunspace($Host)
$PowerShell.Runspace.Open()
$Stopwatch=[Diagnostics.Stopwatch]::new()
$PowerShell.Runspace.SessionStateProxy.PSVariable.Set('Stopwatch',$Stopwatch)
$ScriptBlock=$PowerShell.AddScript{{
Write-Host "$($Stopwatch.ElapsedMilliseconds): $([Threading.Thread]::CurrentThread.ManagedThreadId)"
{Write-Host Deadlock}.Invoke()
}}.Invoke()
$PowerShell.Commands.Clear()
& {
$AsyncResult=$PowerShell.AddScript{Start-Sleep 10}.BeginInvoke()
$Stopwatch.Start()
Write-Host "$($Stopwatch.ElapsedMilliseconds): $([Threading.Thread]::CurrentThread.ManagedThreadId)"
$ScriptBlock.Invoke()
}
脚本块的原始Runspace
最初很忙,但在嵌套脚本块调用之前获得空闲,因此脚本块在当前线程中执行并且不会死锁。
$PowerShell=[PowerShell]::Create()
$PowerShell.Runspace=[RunspaceFactory]::CreateRunspace($Host)
$PowerShell.Runspace.Open()
$Stopwatch=[Diagnostics.Stopwatch]::new()
$PowerShell.Runspace.SessionStateProxy.PSVariable.Set('Stopwatch',$Stopwatch)
$ScriptBlock=$PowerShell.AddScript{{
Write-Host "$($Stopwatch.ElapsedMilliseconds): $([Threading.Thread]::CurrentThread.ManagedThreadId)"
Start-Sleep 10
{Write-Host OK}.Invoke()
}}.Invoke()
$PowerShell.Commands.Clear()
& {
$AsyncResult=$PowerShell.AddScript{Start-Sleep 5}.BeginInvoke()
$Stopwatch.Start()
Write-Host "$($Stopwatch.ElapsedMilliseconds): $([Threading.Thread]::CurrentThread.ManagedThreadId)"
$ScriptBlock.Invoke()
}
要解决此问题,您需要将脚本块与其原始 Runspace
分离。您可以通过使用 [ScriptBlock]::Create
方法从字符串创建新的脚本块来实现。
$PowerShell=[PowerShell]::Create()
$PowerShell.Runspace=[RunspaceFactory]::CreateRunspace($Host)
$PowerShell.Runspace.Open()
$Stopwatch=[Diagnostics.Stopwatch]::new()
$PowerShell.Runspace.SessionStateProxy.PSVariable.Set('Stopwatch',$Stopwatch)
$ScriptBlock=$PowerShell.AddScript{[ScriptBlock]::Create{
Write-Host "$($Stopwatch.ElapsedMilliseconds): $([Threading.Thread]::CurrentThread.ManagedThreadId)"
{Write-Host OK}.Invoke()
}}.Invoke()
$PowerShell.Commands.Clear()
& {
$AsyncResult=$PowerShell.AddScript{Start-Sleep 10}.BeginInvoke()
$Stopwatch.Start()
Write-Host "$($Stopwatch.ElapsedMilliseconds): $([Threading.Thread]::CurrentThread.ManagedThreadId)"
$ScriptBlock.Invoke()
}
我有一个用 PowerShell 编写的表单(因为它在数百台服务器上使用 PS 到 运行 命令)但是这一点让我困惑了一段时间:
$listview.Invoke([System.Action[String]]{
Param
(
[String]$data
)
write-warning "1"
$LVitem = ($LVresults.Items | Where { $_.Name -eq "Test Account" })
write-warning "2"
$LVitem.Tag.Output += $data + "`n"
}, "Testing")
在单独的 运行 空间中,运行 向特定服务器发出调用命令并将输出通过管道传输到此代码块。
现在,如果我 运行 在主 运行 空间(创建表单的地方)中的 Where 语句,它可以很好地工作。在单独的 运行 空间中,它会锁定表单。显示警告1,不显示警告2。
管道到 Foreach 语句有同样的问题,但我可以使用:
Foreach ($item in $listview.Items) {
if ($item.Name -eq "Test Account") { $LVitem = $item }
}
谁能解释一下?我没有对 ListView 或其项目做任何花哨的事情,它只是看起来 ListView 不喜欢它的项目被输送到另一个 运行 空间。
这是 PowerShell 事件系统的问题。当事件处理程序生成带有等待完成选项的事件时,它会死锁。
Register-EngineEvent -SourceIdentifier MyEvent1 -Action {Write-Host 'Does not get called.'}
Register-EngineEvent -SourceIdentifier MyEvent2 -Action {$ExecutionContext.Events.GenerateEvent('MyEvent1',$null,$null,$null,$true,$true)}
New-Event -SourceIdentifier MyEvent2
那么,您的设置与事件有什么关系?
脚本块文字会在创建时记住当前会话状态(包括 Runspace
)。如果在脚本块调用时间当前线程 ([Runspace]::DefaultRunspace
) 的当前 Runspace
不同或忙于处理不同线程中的某些内容,则脚本块通过生成原始 Runspace
的事件来调用并等待完成选项。
另一个有趣的事情是:如果目标Runspace
没有在250毫秒内开始处理事件,那么PowerShell会在等待事件完成的线程中开始事件处理。
在您的代码中有两个嵌套的脚本块调用:
- 脚本块作为委托传递给
$listview.Invoke
方法。 - 传递给
Where-Object
cmdlet 的脚本块。
据我所知,您的代码具有三种可能的结果:
脚本块的原始
Runspace
是免费的,因此脚本块在不同的线程中执行(不是 UI 线程)。$PowerShell=[PowerShell]::Create() $PowerShell.Runspace=[RunspaceFactory]::CreateRunspace($Host) $PowerShell.Runspace.Open() $Stopwatch=[Diagnostics.Stopwatch]::new() $PowerShell.Runspace.SessionStateProxy.PSVariable.Set('Stopwatch',$Stopwatch) $ScriptBlock=$PowerShell.AddScript{{ Write-Host "$($Stopwatch.ElapsedMilliseconds): $([Threading.Thread]::CurrentThread.ManagedThreadId)" {Write-Host OK}.Invoke() }}.Invoke() $PowerShell.Commands.Clear() & { $Stopwatch.Start() Write-Host "$($Stopwatch.ElapsedMilliseconds): $([Threading.Thread]::CurrentThread.ManagedThreadId)" $ScriptBlock.Invoke() }
脚本块的原始
Runspace
很忙,因此脚本块在当前线程中执行并在嵌套脚本块调用时出现死锁。$PowerShell=[PowerShell]::Create() $PowerShell.Runspace=[RunspaceFactory]::CreateRunspace($Host) $PowerShell.Runspace.Open() $Stopwatch=[Diagnostics.Stopwatch]::new() $PowerShell.Runspace.SessionStateProxy.PSVariable.Set('Stopwatch',$Stopwatch) $ScriptBlock=$PowerShell.AddScript{{ Write-Host "$($Stopwatch.ElapsedMilliseconds): $([Threading.Thread]::CurrentThread.ManagedThreadId)" {Write-Host Deadlock}.Invoke() }}.Invoke() $PowerShell.Commands.Clear() & { $AsyncResult=$PowerShell.AddScript{Start-Sleep 10}.BeginInvoke() $Stopwatch.Start() Write-Host "$($Stopwatch.ElapsedMilliseconds): $([Threading.Thread]::CurrentThread.ManagedThreadId)" $ScriptBlock.Invoke() }
脚本块的原始
Runspace
最初很忙,但在嵌套脚本块调用之前获得空闲,因此脚本块在当前线程中执行并且不会死锁。$PowerShell=[PowerShell]::Create() $PowerShell.Runspace=[RunspaceFactory]::CreateRunspace($Host) $PowerShell.Runspace.Open() $Stopwatch=[Diagnostics.Stopwatch]::new() $PowerShell.Runspace.SessionStateProxy.PSVariable.Set('Stopwatch',$Stopwatch) $ScriptBlock=$PowerShell.AddScript{{ Write-Host "$($Stopwatch.ElapsedMilliseconds): $([Threading.Thread]::CurrentThread.ManagedThreadId)" Start-Sleep 10 {Write-Host OK}.Invoke() }}.Invoke() $PowerShell.Commands.Clear() & { $AsyncResult=$PowerShell.AddScript{Start-Sleep 5}.BeginInvoke() $Stopwatch.Start() Write-Host "$($Stopwatch.ElapsedMilliseconds): $([Threading.Thread]::CurrentThread.ManagedThreadId)" $ScriptBlock.Invoke() }
要解决此问题,您需要将脚本块与其原始 Runspace
分离。您可以通过使用 [ScriptBlock]::Create
方法从字符串创建新的脚本块来实现。
$PowerShell=[PowerShell]::Create()
$PowerShell.Runspace=[RunspaceFactory]::CreateRunspace($Host)
$PowerShell.Runspace.Open()
$Stopwatch=[Diagnostics.Stopwatch]::new()
$PowerShell.Runspace.SessionStateProxy.PSVariable.Set('Stopwatch',$Stopwatch)
$ScriptBlock=$PowerShell.AddScript{[ScriptBlock]::Create{
Write-Host "$($Stopwatch.ElapsedMilliseconds): $([Threading.Thread]::CurrentThread.ManagedThreadId)"
{Write-Host OK}.Invoke()
}}.Invoke()
$PowerShell.Commands.Clear()
& {
$AsyncResult=$PowerShell.AddScript{Start-Sleep 10}.BeginInvoke()
$Stopwatch.Start()
Write-Host "$($Stopwatch.ElapsedMilliseconds): $([Threading.Thread]::CurrentThread.ManagedThreadId)"
$ScriptBlock.Invoke()
}