How to Show the Silverlight Busy Indicator When Your Task Has to Run on the UI Thread

December 27, 2011
How to Show the Silverlight Busy Indicator When Your Task Has to Run on the UI Thread

Warning: this is a “hack” to be used when all other options have been exhaustedRecently, I was working on a project that required me to implement export to Excel from a third-party grid. The task was quite simple as the grid control already had a method for the purpose of creating a workbook. However, when using the method from a button click handler my entire UI would freeze for the duration of the export. The export had to be performed on the UI thread, because it relied on a method located on the control (which in turn touched other properties of the control). I knew I couldn’t get around the freeze, but wanted to display a busy indicator. After searching the web I failed to find a solution. The issue is that the busy indicator itself has to use the UI thread to begin animating and Silverlight queues user interface updates (to improve performance), so just because you set the ‘IsBusy’ property true, you’re not guaranteed that the user has seen the indicator.I had some work to do before kicking off the export and was already spawning a background thread via the method ThreadPool.QueueUserWorkItem.However, the only way to get the busy indicator to display was to add a Sleep operation as the first step of my thread and I wasn’t sure how long I needed to sleep before I had some indication that the busy indicator has displayed.My final workaround consisted of the following steps:1) Implement the LayoutUpdated event on the BusyIndicator instance. Inside this event, I set a private bool field to true. The field is marked as volatile to prevent compiler optimizations which sometimes cause multiple threads to see a different value (the alternative would have been to use another field and lock on it when touching the bool).2) In my button click event, I set the private field to false anytime I set the ‘IsBusy’ property of the busy indicator to true.3) I then added this code block at the top of my background task (basically waiting for the busy indicator to update its layout – i.e. display itself) before doing any other work:[sourcecode language="csharp"]while (!_hasBusyIndicatorUpdatedLayout){ Thread.Sleep(100);}[/sourcecode]After exiting this while loop, I kicked off the call to process my Excel export on the UI thread.There are a couple of issues with this solution:1) It’s a hack2) The busy indicator’s animation will be frozen (along with the entire application)However, the alternative was to have a frozen UI all around. I am attaching a sample project implementing all of the above. If you want to see what happens without the check, just comment out the ‘while’ loop shown above.If anyone has come up with a more elegant solution or improves upon this, please comment below.The complate click handler looks like this:[sourcecode language="csharp"]private void ButtonClick(object sender, RoutedEventArgs e){ SetBusy("Exporting data..."); ThreadPool.QueueUserWorkItem((state) => { // this is how we check if the busy indicator has // started to display to the user while (!_hasBusyIndicatorUpdatedLayout) { Thread.Sleep(100); } // now we can perform our busy task DoBusyWorkOnUiThread(); RemoveBusy(); });}[/sourcecode]Download the project below for the full implementation. If you comment out the line in the constructor that does this:[sourcecode language="csharp"]busyIndicator.LayoutUpdated += BusyIndicatorLayoutUpdated;[/sourcecode]You will see that the busy indicator no longer displays.Download Project