这是 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
([Runspace]::DefaultRunspace
Runspace
另一个有趣的事情是:如果 targetRunspace
没有在 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()
}