これは、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
完了まで待機するオプションを使用してオリジナルにイベントを生成することによって呼び出されます。
もう 1 つの興味深い点は、ターゲットRunspace
が 250 ミリ秒以内にイベントの処理を開始しない場合、PowerShell はイベントの完了を待っているスレッドでイベント処理を開始することです。
コードには、ネストされたスクリプト ブロック呼び出しが 2 つあります。
$listview.Invoke
メソッドにデリゲートとして渡されたスクリプト ブロック。
Where-Object
コマンドレットに渡されるスクリプト ブロック。
ご覧のとおり、コードには次の 3 つの結果が考えられます。
スクリプト ブロックのオリジナル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()
}