在单独的运行空间

时间:2015-12-24 11:02:47

标签: winforms listview powershell runspace powershell-v5.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不喜欢它的项目在另一个运行空间中管道。

1 个答案:

答案 0 :(得分: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来调用脚本块并等待完成选项。

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

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

  1. 脚本块作为委托传递给$listview.Invoke方法。
  2. 脚本块传递给Where-Object cmdlet。
  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()
      }
      
    4. 要解决此问题,您需要将脚本块与其原始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()
      }