Last active
July 23, 2018 09:21
-
-
Save alx9r/c24bf08fd22af7e88f3a17e720215f63 to your computer and use it in GitHub Desktop.
Demonstration of parallel loading of modules into PowerShell runspaces where invoking the module scriptblock is expensive.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# All this C# is to synthesize a signal that corresponds to when a runspace is | |
# ready to be used. | |
# see also PowerShell/PowerShell#7034 | |
Add-Type ' | |
using System.Management.Automation; | |
using System.Management.Automation.Runspaces; | |
using System.Threading.Tasks; | |
using System.Collections.Concurrent; | |
using System.Linq; | |
using System; | |
namespace ns | |
{ | |
public class RunspaceStateTransition : IEquatable<RunspaceStateTransition> | |
{ | |
public Nullable<RunspaceAvailability> Availability { get; private set; } | |
public Nullable<RunspaceState> State { get; private set; } | |
public RunspaceStateTransition | |
( | |
Nullable<RunspaceAvailability> availability, | |
Nullable<RunspaceState> state | |
) | |
{ | |
Availability = availability; | |
State = state; | |
} | |
public bool Equals(RunspaceStateTransition other) | |
{ | |
if (other==null) return false; | |
return Availability == other.Availability && | |
State == other.State; | |
} | |
} | |
public static class OpenRunspaceWaiterTask | |
{ | |
public static Task Create(Runspace runspace) | |
{ | |
var tcs = new TaskCompletionSource<object>(); | |
System.EventHandler<RunspaceAvailabilityEventArgs> availabilityHandler = null; | |
System.EventHandler<RunspaceStateEventArgs> stateHandler = null; | |
var log = new ConcurrentQueue<RunspaceStateTransition>(); | |
availabilityHandler = (s,e) => { | |
log.Enqueue( new RunspaceStateTransition(e.RunspaceAvailability,null)); | |
if ( RunspaceIsOpen(runspace.InitialSessionState,log.ToArray()) ) | |
{ | |
runspace.AvailabilityChanged -= availabilityHandler; | |
runspace.StateChanged -= stateHandler; | |
tcs.SetResult(null); | |
} | |
}; | |
stateHandler = (s,e) => { | |
log.Enqueue( new RunspaceStateTransition(null,e.RunspaceStateInfo.State)); | |
if ( RunspaceIsOpen(runspace.InitialSessionState,log.ToArray()) ) | |
{ | |
runspace.AvailabilityChanged -= availabilityHandler; | |
runspace.StateChanged -= stateHandler; | |
tcs.SetResult(null); | |
} | |
}; | |
runspace.AvailabilityChanged += availabilityHandler; | |
runspace.StateChanged += stateHandler; | |
return tcs.Task; | |
} | |
public static bool RunspaceIsOpen( | |
InitialSessionState initialSessionState, | |
RunspaceStateTransition[] log | |
) | |
{ | |
RunspaceStateTransition[] expected = null; | |
if ( initialSessionState == null || | |
initialSessionState.Modules.Count == 0 ) | |
{ | |
expected = new[] { | |
new RunspaceStateTransition(null,RunspaceState.Opening), | |
new RunspaceStateTransition(RunspaceAvailability.Available,null), | |
new RunspaceStateTransition(null,RunspaceState.Opened ) | |
}; | |
} | |
else | |
{ | |
expected = new [] { | |
new RunspaceStateTransition(null,RunspaceState.Opening), | |
new RunspaceStateTransition(RunspaceAvailability.Available,null), | |
new RunspaceStateTransition(null,RunspaceState.Opened ), | |
new RunspaceStateTransition(RunspaceAvailability.Busy,null), | |
new RunspaceStateTransition(RunspaceAvailability.Available,null) | |
}; | |
} | |
return log.SequenceEqual(expected); | |
} | |
} | |
} | |
' -ReferencedAssemblies @( | |
'System.Threading.Tasks' | |
'System.Management.Automation' | |
'System.Collections.Concurrent' | |
'System.Linq' | |
) | |
# Create a module the invokation of whose scriptblock is slow and CPU-bound. | |
$moduleContent = { | |
function fibonacci { | |
param([int]$n) | |
[bigint]$a=0 | |
[bigint]$b=1 | |
foreach ($x in 0..$n) | |
{ | |
$a,$b = $b,($a+$b) | |
} | |
$b | |
} | |
fibonacci 100000 | |
} | |
$modulePath = "$([System.IO.Path]::GetTempPath())slowLoading.psm1" | |
$moduleContent | Set-Content $modulePath | |
Import-Module $modulePath | |
$p = [System.Environment]::ProcessorCount | |
"ProcessorCount: $p" | |
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew() | |
$initialSessionState = [initialsessionstate]::CreateDefault() | |
$initialSessionState.ImportPSModule($modulePath) | |
$runspace = 1..$p | % { [runspacefactory]::CreateRunspace($initialSessionState) } | |
$awaiter = $runspace | % { [ns.OpenRunspaceWaiterTask]::Create($_) } | |
"$($stopwatch.ElapsedMilliseconds)ms : OpenAsync()" | |
$runspace.OpenAsync() | |
"$($stopwatch.ElapsedMilliseconds)ms : WaitAll()" | |
[System.Threading.Tasks.Task]::WaitAll($awaiter) | |
"$($stopwatch.ElapsedMilliseconds)ms : done" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment