0

私は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")

Invoke-Command を特定のサーバーに対して実行し、出力をこのコード ブロックにパイプする別の実行空間にあります。

ここで、メインの実行空間 (フォームが作成される場所) で Where ステートメントを実行すると、完全に正常に動作します。ただし、別の実行空間では、フォームがロックされます。警告 1 は表示され、警告 2 は表示されません。

Foreach ステートメントへのパイプにも同じ問題がありますが、次のものを使用できます。

Foreach ($item in $listview.Items) { 
    if ($item.Name -eq "Test Account") { $LVitem = $item }
}

誰でもこれを説明できますか?私は ListView やその項目で何か特別なことをしていません。ListView はその項目が別の実行空間にパイプされるのを好まないようです。

4

1 に答える 1

3

これは、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 つあります。

  1. $listview.Invokeメソッドにデリゲートとして渡されたスクリプト ブロック。
  2. Where-Objectコマンドレットに渡されるスクリプト ブロック。

ご覧のとおり、コードには次の 3 つの結果が考えられます。

  1. スクリプト ブロックのオリジナル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()
    }
    
  2. スクリプト ブロックのオリジナル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()
    }
    
  3. 元のスクリプト ブロック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()
}
于 2016-01-09T09:07:58.923 に答える