-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Description
This behavior changed between PowerShell versions 5.0 and 5.1. The change was to make PowerShell class method script blocks bound to the runspace in which they were created. If a class instance method is run from within a different runspace then it gets "marshaled" back to the original runspace (via engine events). The result is that if a single class instance is shared among multiple runspaces (e.g., a runspace pool) with the intention to execute instance methods concurrently, the methods run serially instead.
I don't know why this changed between 5.0 and 5.1 but it looks to be by design. @vors can you please comment?
Repro steps:
PS > (measure-command { .\RunspaceInstanceTest.ps1 }).TotalSeconds
RunspaceInstanceTest.ps1 file script:
class Writer
{
static WriteLineS([string] $Msg)
{
for ($i=0; $i -lt 10; $i++)
{
[Console]::WriteLine("Static: Loop $i - $Msg")
Start-Sleep -MilliSeconds 100
}
}
WriteLineI([string] $msg)
{
for ($i=0; $i -lt 10; $i++)
{
[Console]::WriteLine("Instance: Loop $i - $Msg")
Start-Sleep -MilliSeconds 100
}
}
}
$script = @'
param ([object] $writerInstance, [string] $Title)
class Writer
{
static WriteLineS([string] $Msg)
{
for ($i=0; $i -lt 10; $i++)
{
[Console]::WriteLine("Static: Loop $i - $Msg")
Start-Sleep -MilliSeconds 100
}
}
WriteLineI([string] $msg)
{
for ($i=0; $i -lt 10; $i++)
{
[Console]::WriteLine("Instance: Loop $i - $Msg")
Start-Sleep -MilliSeconds 100
}
}
}
# Create new instance
#$writerInstance = [Writer]::new()
# Instance write
$writerInstance.WriteLineI("$Title")
# Static write
#[Writer]::WriteLineS("$Title")
'@
$writer = [Writer]::new()
$rsp = [runspacefactory]::CreateRunspacePool(1, 10, $host)
$rsp.Open()
class Task
{
[powershell] $powershell
[System.IAsyncResult] $Async
}
$tasks = @()
1..5 | foreach {
$task = [Task]::new()
$tasks += $task
$task.powershell = [powershell]::Create()
$task.powershell.RunspacePool = $rsp
$task.Async = $task.powershell.AddScript($script).AddArgument($writer).AddArgument("Task $_").BeginInvoke()
}
foreach ($task in $tasks)
{
$task.powershell.EndInvoke($task.Async)
$task.powershell.Dispose()
}
$rsp.Dispose()
Expected:
5 concurrently running 1 second loops (with sleep) should take about 1 second to run.
Actual Result:
It takes about 5 seconds to run indicating that the concurrent scripts are running serially.