Skip to content

Instantly share code, notes, and snippets.

@alx9r
Last active July 23, 2018 09:21
Show Gist options
  • Save alx9r/c24bf08fd22af7e88f3a17e720215f63 to your computer and use it in GitHub Desktop.
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.
# 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