CTP3: The [RunspaceFactory] and [PowerShell] Accelerators

You might have noticed these two accelerators in the list I published recently along with the technique how to add your own type accelerators. It’s not immediately clear that they are related, but they very much are. This is going to be a fairly developer-oriented post, so there won’t be any hand-holding here.

[PowerShell]

The [PowerShell] accelerator is aliased to System.Management.Automation.PowerShell. It has one static method, Create(), which returns an instance of the class itself. In v1, if you wanted to set up asynchronous pipelines and do other fancy stuff, you had to get your hands dirty with Runspace and Pipeline instances and know how to wire them all together. The [PowerShell] class makes it a lot easier to do this; think of it a “PowerShell Pipeline Runner” class. At its simplest, you can just “new up” an instance, assign some script using the AddScript method and call Invoke(). You can run this asynchronously using the BeginInvoke/EndInvoke pattern which should be familiar to all .NET developers who’ve done a bit of threading work.

  1. # create a new pipeline  
  2. $ps = [powershell]::create()  
  3.  
  4. # add a command (returns Command object)  
  5. [void] $ps.AddScript("ls")  
  6.  
  7. # invoke synchronously, returning results of "ls."  
  8. $results = $ps.Invoke()  
  9.  
  10. # clean up  
  11. $ps.dispose() 

[RunspaceFactory]

This accelerator is aliased to System.Management.Automation.Runspaces.RunspaceFactory. It has two static methods of interest, CreateRunspace and CreateRunspacePool. I’m going to focus on the latter because it has more interesting uses which you will see. This latter method lets you create a collection of Runspace instances that are essentially reusable. The great thing is that you don’t have to worry about any of the details. It just works; this leads me to the next part: queuing local pipeline jobs to be run in the background.

The pool you create can be constrained in many ways by using the various overloads of the CreateRunspacePool. You can even pass it a RunspaceConnectionInfo object so that the queued pipelines are run on remote servers. This is done by using the New-PSSession cmdlet to create a session to a remote machine running PowerShell 2.0 with WinRM configured correctly.

Queueing Pipelines to a Runspace Pool

This is where the magic really happens. Simply new up a PowerShell instance, assign the pool to it and run a command. New up as many PowerShell instnaces as you like, and as long as you assign each of them the same pool, the pool automagically looks after processing them as fast as it can and will never go over its hard limits you give it for the number of simultaneous runspaces. In this next script, I set up a pool of three runspaces. I then queue up six pipelines to be run. I am using the BeginInvoke method to start the command in the background. You’ll see when it runs that each command will make a beep of a certain frequency when it finally starts up. You can hear the first three jobs start up pretty much straight away, as each completes, another starts up. Magic!

  1. #require -version 2.0  
  2.  
  3. # create a pool of 3 runspaces  
  4. $pool = [runspacefactory]::CreateRunspacePool(1, 3)  
  5. $pool.Open()  
  6.  
  7. write-host "Available Runspaces: $($pool.GetAvailableRunspaces())" 
  8.  
  9. $jobs = @()  
  10. $ps = @()  
  11. $wait = @()  
  12.  
  13. # run 6 background pipelines  
  14. for ($i = 0; $i -lt 6; $i++) {  
  15.      
  16.    # create a "powershell pipeline runner"  
  17.    $ps += [powershell]::create()  
  18.      
  19.    # assign our pool of 3 runspaces to use  
  20.    $ps[$i].runspacepool = $pool 
  21.      
  22.    $freq = 440 + ($i * 10)  
  23.    $sleep = (1 * ($i + 1))  
  24.      
  25.    # test command: beep and wait a certain time  
  26.    [void]$ps[$i].AddScript(  
  27.         "[console]::Beep($freq, 30); sleep -seconds $sleep")  
  28.      
  29.    # start job  
  30.    write-host "Job $i will run for $sleep second(s)" 
  31.    $jobs += $ps[$i].BeginInvoke();  
  32.      
  33.    write-host "Available runspaces: $($pool.GetAvailableRunspaces())" 
  34.      
  35.    # store wait handles for WaitForAll call  
  36.    $wait += $jobs[$i].AsyncWaitHandle  
  37. }  
  38.  
  39. # wait 20 seconds for all jobs to complete, else abort  
  40. $success = [System.Threading.WaitHandle]::WaitAll($wait, 20000)  
  41.  
  42. write-host "All completed? $success" 
  43.  
  44. # end async call  
  45. for ($i = 0; $i -lt 6; $i++) {  
  46.  
  47.     write-host "Completing async pipeline job $i" 
  48.  
  49.     try {  
  50.  
  51.         # complete async job  
  52.         $ps[$i].EndInvoke($jobs[$i])  
  53.  
  54.     } catch {  
  55.       
  56.         # oops-ee!  
  57.         write-warning "error: $_" 
  58.     }  
  59.  
  60.     # dump info about completed pipelines  
  61.     $info = $ps[$i].InvocationStateInfo  
  62.  
  63.     write-host "State: $($info.state) ; Reason: $($info.reason)" 
  64. }  
  65.  
  66. # should show 3 again.  
  67. write-host "Available runspaces: $($pool.GetAvailableRunspaces())" 

Feel free to post questions or requests for clarification, but I did say it was a bit tough and developer-oriented. I have a hint that our good friend and fellow MVP, Karl Prosser (aka Klumsy), will be wrapping up some of this stuff into some nice admin-oriented background  command functions.

IMPORTANT:

Due to the WaitAll call at line 40, this script will not work in an STA thread (i.e. in PowerShell ISE). Use PowerShell.EXE to run this script. Everything else will work in ISE fine.

Have fun!

blog comments powered by Disqus

About the author

Irish, PowerShell MVP, .NET/ASP.NET/SharePoint Developer, Budding Architect. Developer. Montrealer. Opinionated. Montreal, Quebec.

Month List

Page List