More potential use cases for thread-safe variable access in PowerShell: Events (Except, not really…)

13-May-2014 Update: Turns out, this whole post was wrong. 🙂 While event handlers in PowerShell appear to be asynchronous from your script / console’s point of view (meaning that they can execute in between PowerShell statements that you’re running), the PowerShell engine actually does all of this on a single thread (at least when it’s in STA mode, which is the default in PowerShell 3.0 and later.) This is good news, and bad news.

The good news is that you don’t have to worry about using Concurrent or Synchronized collections (or Lock-Object), because there’s not any true multithreading happening here. Only one event handler or statement from your script will be accessing a given file or global variable at a time.

The bad news is that there’s not really a good way to achieve any kind of synchronization between the event handlers and the main script code. Event handlers could be processed at any time, from your script’s point of view, and you can’t group a set of statements together to make them atomic. This might cause some unexpected bugs, depending on how your script is written. However, you can still use a Queue similar to the one shown in the rest of this post (though it could be just a plain old Queue collection; doesn’t need to be ConcurrentQueue or Synchronized.) Your event handlers queue up events, and your main code processes them whenever it can. As it turns out, this is very much like how the PowerShell engine is handling the original .NET events and passing them on to your PowerShell event handlers in the first place.

Here’s the original blog post:


The first day of the 2014 NA PowerShell Summit has come and gone. One of the sessions I attended today was Matt Graber’s “Advanced PowerShell Eventing Scripting Techniques”. In that session, Matt covered the fact that you can’t return values from the asynchronous Action blocks that are used to respond to events, because PowerShell would have no way to keep that output from interfering with whatever was happening in the main thread at the time. (The event might fire while you were right in the middle of executing some other command, with unknown results.)

However, something I didn’t know was that it is possible to write to Global variables from those action blocks, if you needed to somehow pass an object back to the main PowerShell runspace. Hello, again, concurrency! To do that safely, you need to make sure that the global variable is used in a thread-safe manner, so that multiple threads writing to it simultaneously doesn’t cause a problem, and also so that reading from the variable from the main thread isn’t broken if a background thread writes at the same time.

Here’s an example of how you might accomplish this, using a System.Collections.Concurrent.ConcurrentQueue . (This is a high performance thread-safe collection class added in .NET 4.0 / PowerShell 3.0. It’s possible to do something similar with PowerShell 2.0 compatibility as well.)

I’m having trouble creating a Gist from my company laptop for some reason, so I’ll just put the code here for now and move it later if it’s hard to read.

$global:MessageQueue = New-Object System.Collections.Concurrent.ConcurrentQueue[psobject]

$fsw = New-Object System.IO.FileSystemWatcher -Property @{
    Path                  = $env:TEMP
    Filter                = '*.tmp'
    IncludeSubdirectories = $true
}

$job = Register-ObjectEvent -InputObject $fsw -EventName Created -Action {
    $object = New-Object psobject -Property @{
        Path = $EventArgs.FullPath
    }

    $global:MessageQueue.Enqueue($object)
}

# At this point, an unknown number of background runspaces might be responding to events as
# new .tmp files are created (such as when a process calls [System.IO.Path]::GetTempFileName().
# Because ConcurrentQueue is thread-safe, these runspaces won't interfere with each other.

# Now, we'll respond to the queue in our main thread.  (For demonstration purposes, we'll
# abort after 5 such files have been created.)  This allows us to access the live objects
# that were created in the -Action block.

for ($i = 0; $i -lt 5;)
{
    $object = $null

    if ($global:MessageQueue.TryDequeue([ref] $object))
    {
        $i++

        # We'll just output the object to the screen, rather than doing anything important with it.
        $object
    }
    else
    {
        Start-Sleep -Milliseconds 20
    }
}

# Clean up

$fsw.Dispose()
$job | Remove-Job -Force

This sets up our concurrent queue and event registration, then begins a loop to respond to the first 5 new tmp files that are created for the current user. If other processes are actively using temp files, this might happen all on its own, but for my test, I opened up a second PowerShell window and ran this code to help it along:

for ($i = 0; $i -lt 10; $i++)
{
    $path = [System.IO.Path]::GetTempFileName()
    Remove-Item -Path $path -Force
}

In this example, I let the .NET concurrent collection class do all the heavy lifting, but for other scenarios, this could be another place to use Lock-Object to achieve your own custom synchronized behavior.

Advertisements

About Dave Wyatt

Microsoft MVP (PowerShell), IT professional.
This entry was posted in PowerShell and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s