在不同的运行空间管道的ListView项目 [英] Piping ListView items in separate runspace

查看:197
本文介绍了在不同的运行空间管道的ListView项目的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经写在PowerShell中(因为它使用PS来运行超过数百台服务器的命令)形式,但此位曾难倒我了一段时间:

  $ listview.Invoke([System.Action [字符串]] {
    参数
    (
        [字符串] $数据
    )
    写警告1
    $ LVITEM =($ LVresults.Items |其中{$ _。名称-eq测试帐户})
    写警告2
    $ LVitem.Tag.Output + = $数据+'N
},测试)

这是在运行一个调用命令到一个特定的服务器和管道输出到此code座独立的运行空间。

现在,如果我运行的主要运行空间(在其中创建表格)的Where语句它完美的罚款。在独立的运行空间但它锁定了的形式。警告1显示,警告2家是没有的。

管路foreach语句有同样的问题,但我可以用:

  Foreach源($在$ listview.Items项){
    如果($ item.Name -eq测试帐号){$ LVITEM = $项目}
}

有人能解释一下吗?我没有做任何幻想与ListView控件或它的项目,它只是似乎ListView控件不喜欢被管道在另一个运行空间的项目。


解决方案

这是使用PowerShell事件系统的问题。它的死锁事件处理程序等待完成选项生成事件。

 注册-EngineEvent -SourceIdentifier MyEvent1 -Action {写主机不会被调用。'}
注册-EngineEvent -SourceIdentifier MyEvent2 -Action {$ ExecutionContext.Events.GenerateEvent('MyEvent1',$空,空$,$ NULL,$真,真$)}
新事件-SourceIdentifier MyEvent2

那么,你的设置有活动呢?

脚本块文字​​记忆当前会话状态(包括运行空间)的创建时间。如果在脚本块调用时电流运行空间为当前线程( [运行空间] :: DefaultRunspace )不同,或者忙在不同的线程处理的东西,那么脚本块通过生成事件原运行空间与等待完成选项调用。

另一个有趣的事情是:如果目标运行空间不在250毫秒开始处理事件,然后PowerShell的启动事件处理的线程等待事件完成。

在您的code你有两个嵌套的脚本块invokations:


  1. 脚本块作为委托传递给 $ listview.Invoke 方法。

  2. 脚本块传递给位置对象 cmdlet的。

在我看,你的code有三种可能的结果:


  1. 脚本块的原运行空间是免费的,所以脚本块在不同的线程(不是UI线程)执行。

      $的PowerShell = [PowerShell中]:创建()
    $ PowerShell.Runspace = [RunspaceFactory] ​​:: CreateRunspace($主机)
    $ PowerShell.Runspace.Open()
    $秒表= [Diagnostics.Stopwatch] ::新()
    $ PowerShell.Runspace.SessionStateProxy.PSVariable.Set('秒表',$秒表)
    $脚本块= $ PowerShell.AddScript {{
        写主机$($ Stopwatch.ElapsedMilliseconds):$([Threading.Thread] :: CurrentThread.ManagedThreadId)
        {写主机OK】.Invoke()
    }}。调用()
    $ PowerShell.Commands.Clear()
    &安培; {
        $ Stopwatch.Start()
        写主机$($ Stopwatch.ElapsedMilliseconds):$([Threading.Thread] :: CurrentThread.ManagedThreadId)
        $ ScriptBlock.Invoke()
    }


  2. 脚本块的原运行空间忙,所以脚本块在当前线程和嵌套的脚本块调用死锁执行。

      $的PowerShell = [PowerShell中]:创建()
    $ PowerShell.Runspace = [RunspaceFactory] ​​:: CreateRunspace($主机)
    $ PowerShell.Runspace.Open()
    $秒表= [Diagnostics.Stopwatch] ::新()
    $ PowerShell.Runspace.SessionStateProxy.PSVariable.Set('秒表',$秒表)
    $脚本块= $ PowerShell.AddScript {{
        写主机$($ Stopwatch.ElapsedMilliseconds):$([Threading.Thread] :: CurrentThread.ManagedThreadId)
        {写主机死机} .Invoke()
    }}。调用()
    $ PowerShell.Commands.Clear()
    &安培; {
        $ AsyncResult = $ {PowerShell.AddScript启动休眠10} .BeginInvoke()
        $ Stopwatch.Start()
        写主机$($ Stopwatch.ElapsedMilliseconds):$([Threading.Thread] :: CurrentThread.ManagedThreadId)
        $ ScriptBlock.Invoke()
    }


  3. 脚本块的原运行空间开始忙,但得到嵌套的脚本块调用之前免费的,所以在当前线程中执行脚本块并不会死锁。

      $的PowerShell = [PowerShell中]:创建()
    $ PowerShell.Runspace = [RunspaceFactory] ​​:: CreateRunspace($主机)
    $ PowerShell.Runspace.Open()
    $秒表= [Diagnostics.Stopwatch] ::新()
    $ PowerShell.Runspace.SessionStateProxy.PSVariable.Set('秒表',$秒表)
    $脚本块= $ PowerShell.AddScript {{
        写主机$($ Stopwatch.ElapsedMilliseconds):$([Threading.Thread] :: CurrentThread.ManagedThreadId)
        启动睡眠10
        {写主机OK】.Invoke()
    }}。调用()
    $ PowerShell.Commands.Clear()
    &安培; {
        $ AsyncResult = $ {PowerShell.AddScript启动睡眠5} .BeginInvoke()
        $ Stopwatch.Start()
        写主机$($ Stopwatch.ElapsedMilliseconds):$([Threading.Thread] :: CurrentThread.ManagedThreadId)
        $ ScriptBlock.Invoke()
    }


要解决此问题,你需要从原来的运行空间分离脚本块。您可以使用 [脚本块]:创建方法从字符串创建新的脚本块实现这一目标。

  $的PowerShell = [PowerShell中]:创建()
$ PowerShell.Runspace = [RunspaceFactory] ​​:: CreateRunspace($主机)
$ PowerShell.Runspace.Open()
$秒表= [Diagnostics.Stopwatch] ::新()
$ PowerShell.Runspace.SessionStateProxy.PSVariable.Set('秒表',$秒表)
$脚本块= $ {PowerShell.AddScript [脚本块] :: {创建
    写主机$($ Stopwatch.ElapsedMilliseconds):$([Threading.Thread] :: CurrentThread.ManagedThreadId)
    {写主机OK】.Invoke()
}}。调用()
$ PowerShell.Commands.Clear()
&安培; {
    $ AsyncResult = $ {PowerShell.AddScript启动休眠10} .BeginInvoke()
    $ Stopwatch.Start()
    写主机$($ Stopwatch.ElapsedMilliseconds):$([Threading.Thread] :: CurrentThread.ManagedThreadId)
    $ ScriptBlock.Invoke()
}

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.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")

It's in a separate runspace that runs an Invoke-Command to a specific server and pipes the output to this code block.

Now if I run the Where statement in the main runspace (where the form is created) it works perfectly fine. In the separate runspace however it locks up the form. Warning 1 is displayed, Warning 2 is not.

Piping to a Foreach statement has the same problem, but I can use:

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

Can anyone explain this? I'm not doing anything fancy with the ListView or its items, it just seems the ListView doesn't like its items being piped in another runspace.

解决方案

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

这篇关于在不同的运行空间管道的ListView项目的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆