Piping ListView items in separate runspace

后端 未结 1 972
长发绾君心
长发绾君心 2020-12-12 07:40

I have a form written in PowerShell (as it uses PS to run commands over hundreds of servers) but this bit had me stumped for awhile:

$listview.Invoke([System         


        
相关标签:
1条回答
  • 2020-12-12 08:14

    This is the problem with PowerShell event system. It deadlocks when event handler generate event with wait to completion option.

    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
    

    So, what your setup have to do with events?

    Script blocks literals remembers current session state (including Runspace) at creation time. And if at script block invocation time current Runspace for current thread ([Runspace]::DefaultRunspace) is different or busy processing something in different thread, then script block invoked by generating event to original Runspace with wait to completion option.

    Another interesting thing is that: if target Runspace does not start processing events in 250 milliseconds, then PowerShell start event processing in the thread waiting for event completion.

    In your code you have two nested script block invokations:

    1. Script block passed as delegate to $listview.Invoke method.
    2. Script block passed to Where-Object cmdlet.

    As I see, your code have three possible outcomes:

    1. Script block's original Runspace is free, so script block executed in different thread (not UI thread).

      $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. Script block's original Runspace is busy, so script block executed in current thread and deadlocks on nested script block invocation.

      $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. Script block's original Runspace initially busy, but get free before nested script block invocation, so script block executed in current thread and does not deadlocks.

      $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()
      }
      

    To workaround this issue you need to detach script block from its original Runspace. You can achieve that by creating new script block from string using [ScriptBlock]::Create method.

    $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()
    }
    
    0 讨论(0)
提交回复
热议问题