PowerShell Wrestling with Asynchronous Programming for Dummies

A series of scripts I have been working on recently forced me to get my head, at least start the process of getting my head around, how delegates work in PowerShell. Now, if youre not a developer, and, have no idea what delegates are or how they work, youre not alone. I had read a lot of C# books and still didnt get them. The main idea I have heard is that delegates are "function pointers". In practical terms that made no sense to me, but, I have settled on the idea that delegates are a way to call methods, functions or script blocks in response to events. For instance, in Windows, events are a huge concept. In fact, Windows, in some frameworks, is an event-driven OS. That is to say, when a user takes an action it is termed an event. Where delegates come into play is that other actions can be triggered by events. So, if you do action X, Windows broadcasts that event X has occurred. If any events are set to fire when event X occurs, delegates are the mechanism by which the OS recognizes an event has taken place and an action needs to take place.

Ok, enough theory. My brain hurts trying to put that idea into words. Lets look at some more practical, real concepts. While going back and forth with Oisin Grehan and Kirk Munro to try and figure out these delegates Kirk pointed me to some code he had written for PowerGUI in the Script Editor Essentials Add-On. Within the Add-on.ScriptEditorEssentials.psm1 file, Kirk noted over on the Powershell.org forums,
Looking for the PowerShell way to write/add delegates
that I should look for the $onTabChanged signature. After downloading the zip I found this:
[System.EventHandler]$onTabChanged = {
      & {
            $currentDocumentWindow = $PGSE.CurrentDocumentWindow
            if ($commandsEnabled = ($currentDocumentWindow -ne $null)) {
                  Update-ScriptEditorWordWrapInternal -DocumentWindow $currentDocumentWindow
                  Update-ScriptEditorViewWhitespaceInternal -DocumentWindow $currentDocumentWindow
                  Update-ScriptEditorVirtualWhitespaceInternal -DocumentWindow $currentDocumentWindow
            }
            foreach ($commandName in @(EditCommand.ViewWhitespace,EditCommand.WordWrap,EditCommand.VirtualWhitespace)) {
                  if ($command = $PGSE.Commands[$commandName]) {
                        $command.Enabled = $commandsEnabled
                  }
            }
            Update-FileEncodingInternal -DocumentWindow $currentDocumentWindow
            Update-ScriptEditorTabStripScrollingInternal
      }
}
Now, when I looking at some other code, I did not see an explicit typing of the code block. The way outlined here explicitly defines the block as an EventHandler. Another example shows how to take parameters so you can reference them in your scriptblock. I am only going to show the top portion to abbreviate the code block,
[System.EventHandler[Quest.PowerGUI.SDK.CommandEventArgs]]$onOpening = {
      param(
            $sender,
            $eventArgs
      )
      & {
What is interesting here is the inclusion of a $sender and $eventArgs parameters. In my own usage I did this,
[System.Xml.Schema.ValidationEventHandler] $onValidationError = {
      param(
            $sender,
            $eventArgs
      )
     
      $isValid = $false;
      Write-Verbose "Error: $($eventArgs.Message)";
}
where $eventArgs.Message zeroes in on the specific message passed from the main functions handling of the   Validation Event. In my case, I associated the ValidationEventHandler with error events. So, when Validation Events (i.e., validation events) occurred, the boolean object, $isValid, was switched from $true to $false, and, the Verbose pipeline output the specific error message to the host. As noted below, Write-Output will not work, but, thankfully, in PowerShell, there are other pipelines to consider as options. In my case, I just went with -Verbose.

As noted by Kirk in the forum post, I was unable to use Write-Output from within my delegate to return output to the host because delegate objects are suppressed. This prevents items placed on the pipeline, in this case via Write-Output, from reaching the end user.
Delegates work differently than regular scripts. I believe any output that you try to return from them will be suppressed. You should be able to set module-scoped (script:...) variables from them though, and IIRC you should be able to use Write-Host to write messages back to the host (which is probably what you really want to do here instead of Write-Output). Or better yet, Write-Debug or Write-Verbose. But still, the point is the same. You cant simply return objects from a delegate and expect them to appear because you dont have control over how that works. See http://social.msdn.microsoft.com/Forums ... 9d90c20737 for more details about that.
When I went to read through that post I saw its title, "How to get return value from delegate?", and I quickly realized why I had to take a slightly more circuitous route to get output back in front of the user. Indeed, a step from the more basic, what I tend to think of as "procedural" type scripting where you are just mimicing user actions with commands in a linear fashion to the more abstracted, but, powerful  forces you, as a scripter, to start to think of things like jobs, queues, and events. All of these are, in their various forms, a step towards becoming familiar with the asychronous programming model (APM).

That procedural approach I mentioned is much more then synchronous approach. I perform an action, single-threading tasks, one by one, into a script. When I run the script they run in order and result in an action. The problem with this model, for which APM offers alternatives and opportunities, is the capacity to tap into classes, methods and cmdlets which allow you to take actions and, thank to the abstracted nature of APM, be able to move on with your script and not wait for the previous task to complete. The challenge, however, is a bit of a learning curve. With this kind of abstraction comes a good bit of learn. But, if you really want to take steps towards the quantum leap in scripting capabilities, you have to wrestle with the APM, in one form or another, at some time.

The code listed above is a few steps into that APM discussion, but, offers a perfect demonstration of what you can expect, both in terms of challenges and benefits. When you start to get your head around these types of concepts you will find yourself capable of fully pushing the limits of a machines CPU and memory. No longer will scripts be written where time is an issue, but, rather, resources and design are the limitations of your scripting capabilities. PowerShell offers some cmdlet based, that is to say, encapsulated/fully managed, opening steps. As you begin to explore and understand things like jobs you will find that the asynchronous dimension takes time as the "long" dimension in a problem set and rotates it 90 degrees, effectively neutralizing the difficulties that come with programming as linear speed.

Although the following link is focused on C# it offers a good starting point on how to think about delegates and event handling,
Delegates and Event Handling in C#
Hopefully, as I explore more about delegates, I can offer more insight into how these really work in PowerShell and other ways we can take advantage of the APM. Some further excellent links are listed below:

  • Use Asynchronous Event Handling in PowerShell
  • WPF & PowerShell -- Part 3 (Handling Events)
  • Manage Event Subscriptions with PowerShell

Related Posts by Categories

0 comments:

Post a Comment