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]::DefaultRunspaceRunspace

另一个有趣的事情是:如果 targetRunspace没有在 250 毫秒内开始处理事件,那么 PowerShell 在等待事件完成的线程中开始处理事件。

在您的代码中,您有两个嵌套的脚本块调用:

  1. 脚本块作为委托传递给$listview.Invoke方法。
  2. 传递给Where-Objectcmdlet 的脚本块。

如我所见,您的代码有三种可能的结果:

  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 回答