Nathan Ziehnert

7 minute read

Now that we have a good handle on creating non-smelly layouts let’s talk about something equally important - user experience or UX for short. Consider the following experience that I’m sure we’ve all experienced at least one time:

You find yourself running an application, let’s say Microsoft Outlook (bear with me, I know Outlook never crashes, but this is just an example) and you go to perform something that has the potential to take a long time or a lot of resources… searching your 8 years worth of saved emails (every lawyer reading this just shuddered at the thought of all that legal hold) for a particular email. You type your entry into the search bar and press your enter key and after a few seconds you notice that nothing is happening. Then you fast and furiously click your pointer all over the window to get some sort of response. Nothing. Then it happens. The window starts to look pale and you see those dreaded words at the top of the window… “Not Responding”

You have your finger on the button that will pull the life support off the application - because nothing but a miracle now will save it now. A millisecond before you hit the “End Process” button to pull the plug the app springs back to life. You rejoice.

So what happened?

 

Pull this thread, as I walk away…

WPF applications are “single” threaded to begin with. I put single in quotes because it’s actually two threads:

  1. The Render Thread: Responsible for rendering the user interface on screen.
  2. The UI Thread: Responsible for “painting” the UI, monitoring for events (button clicks, mouse in/out, etc.), and performing any actions that events have associated with them - basically everything except actually rendering the UI on screen.

Whenever the UI thread has to process something that takes more than a few cycles, it is unable to do any of the other tasks it has been given responsibility over (like painting the UI). This is why the UI thread “freezes” or “locks up” or “hangs” and eventually becomes unresponsive. This could be because it has a long running task or because of a bug in the code. For first time application developers (and by extension first time PowerShell GUI builders) this is typically because they’ve tied some long running process to an event. Think “grab all users from AD when I press the “Execute” button”.

Visio Diagram of Thread Flow

In the overly-simplified diagram above, when we reach the “Did the user do something” phase, the UI thread just waits for an event and repaints the UI. As soon as a subscribed event takes place however (such as a button press) the UI thread stops repainting the UI and executes whatever code happens in the event subscription until completion and then returns back to waiting for events and repainting the UI. So if you run a long process the UI never repaints the window and the user thinks the app just stopped working.

Okay, so how do we fix it?

 

And I will try… to fix you…

The magical solution might surprise you… actually probably not.

Multi-Threading

We need to break out our long running process into a separate thread so that the UI thread can remain responsive. This is relatively easy to do in PowerShell - we just create a job, wait for it to finish, and then get the results. You’ve probably even done this before.

Here’s the rub though. WPF controls are not thread-safe.

You: “I have no idea what that means.”

Basically, a thread-safe object means that any thread could make changes to that object at the same time and no issues would occur. Because this cannot be guaranteed for a WPF control, the UI thread OWNS all of the object and is the only one allowed to make changes to them.

You: “Why is this a problem?”

Say you want to report on progress of your long running process. The user friendly UX way to do this is to update a progress bar. However, your UI thread is not monitoring your long running process so it has no idea where it is at in it’s process, and the long running process thread cannot access the progress bar directly to update it.

The solution is in another piece of the UI thread that we have not yet talked about… the dispatcher. The dispatcher owns the prioritization of work items on the UI thread (changing the button when mouse hovers, moving the window, checking for events etc.) and there are 11 different potential priority levels for work items. Other threads can talk to the dispatcher and request that work items be added to the queue and the dispatcher will prioritize that work.

So how do we do it?

 

This is how we doooo it…

When the PoSHPF script starts a custom class is created called SyncClass. This class has one property and two methods - the property is a synchronized hashtable containing all of the windows and controls which are created from the XAML files, and the methods of CloseWindow and UpdateElement talk to the dispatcher to add work items to the UI thread queue. This class is instantiated in a variable called $Global:SyncClass.

Additionally, a function is defined called Start-BackgroundScriptBlock which allows this SyncClass to be shared (or synchronized) between threads. This function creates a new runspace sharing the $Global:SyncClass variable with it, adds the script block specified in the parameter to the runspace, and then adds the runspace to a list of runspaces to be monitored for cleanup (when the script execution completes).

In PoSHPF you would do something like the following to execute a long running branch of code:

$sb = {
    $SyncClass.UpdateElement("formMainWindowControlStartButton","IsEnabled",$false)
    $SyncClass.UpdateElement("formMainWindowControlProgressRing","Visibility","Visible")
    for($i=0;$i -le 10000;$i++){
        if($i % 500 -eq 0){$SyncClass.UpdateElement("formMainWindowControlProgressBar","Value",($i/10000)*100)}
    }
    $SyncClass.UpdateElement("formMainWindowControlProgressRing","Visibility","Hidden")
    $SyncClass.UpdateElement("formMainWindowControlStartButton","IsEnabled",$true)
}
Start-BackgroundScriptBlock -scriptBlock $sb

Let’s break the script block down a bit. We’re effectively doing 5 different things:

  1. We’re disabling the “start” button (of our GUI not Windows) so that the user cannot accidentally kick off the process twice in a row by calling the the SyncClass UpdateElement method.
  2. Then we make a progress ring (the spinning ring) visible to the end user so that they know the process is still running with some visual feedback calling that same UpdateElement method.
  3. Next we execute our long running process. In this case we’re just having the computer count to 10,000. This could be any arbitrary set of code.
  4. While the computer counts to 10,000, every 500 counts the progress bar gets updated to provide the user with additional feedback on the process. This again is done through the UpdateElement method.
  5. After the process completes, we hide the progress ring and re-enable the start button. You guessed it, this is done through the UpdateElement method again.

To execute the script block in a separate thread all we then have to do is run the Start-BackgroundScriptBlock function with the -scriptBlock parameter defined as a script block.

Here’s the basic use of both the applicable methods and the function:

  • UpdateElement: Call it from the SyncClass variable. Generally inside of a script block you intend to run in the background.
    • $SyncClass.UpdateElement("NAMEOFCONTROL","PROPERTYTOUPDATE",PROPERTYVALUE)
    • NAMEOFCONTROL: The full name of the control (e.g. formMainWindowControlStartButton)
    • PROPERTYTOUPDATE: The name of the property to update (e.g. IsEnabled, Content, Text)
    • PROPERTYVALUE: The new value of the property (e.g. $True, “Some Text”)
  • CloseWindow: Call it from the SyncClass variable. Generally inside of a script block you intend to run in the background.
    • $SyncClass.CloseWindow("NAMEOFWINDOW")
    • NAMEOFWINDOW: The full name of the WPF window (e.g. formMainWindow)
  • Start-BackgroundScriptBlock: Generally called inside of an EventHandler such as the click handler on a button.
    • Start-BackgroundScriptBlock -scriptBlock $sb
    • sb: The script block you want to execute in a background runspace.

That’s basically it. All the heavy lifting (creating runspaces, cleaning up after them, etc.) is all done for you by the framework. You just have to know what you want to do and the names of the controls you wish to update from the background thread. Easy-peasy.

 

Closing thoughts

Since this post is already pretty long, I haven’t broken down the internals of how the framework is doing all of this. In a future post if there is interest I’d be happy to dive in a bit further. However, most of the groundwork for how this is structured in PoSHPF was “borrowed” from Boe Prox and Stephen Owen. Feel free to peek at their solutions to get an idea of how what we’re doing in PoSHPF is possible.

And as always, Happy Admining!

comments powered by Disqus

Table of Contents