WPF Asynchronous Wait Cursor using Task Parralel Library

While working on a WPF application, I had a need to show the wait cursor while performing a long running operation. You would expect the code to look as follows:

				
System.Windows.Input.Cursor previousCursor = someWindow.Cursor;
someWindow.Cursor = System.Windows.Input.Cursors.Wait;
try
{
    LongRunningOperation();
}
finally
{
    someWindow.Cursor = previousCursor;
}

		

However, I didn't want the long running process tying up the UI thread. I needed to run the long running operation on its own thread. I decided to use the Task Parallel Library (TPL) to help.

				
System.Windows.Input.Cursor previousCursor = someWindow.Cursor;
someWindow.Cursor = System.Windows.Input.Cursors.Wait;
System.Threading.Tasks.Task.Factory.StartNew(() =>
{
   LongRunningOperation();
}).ContinueWith((t) =>
{
   someWindow.Dispatcher.BeginInvoke(new Action(() => someWindow.Cursor = previousCursor));
});

		

Note that I had to use Dispatcher.BeginInvoke since the ContinueWith code is running on another thread.

After writing this code, I quickly realized that I could use this functionality in multiple places. Therefore, I came up with the following helper method:

code>
using System;
using System.Threading.Tasks;
using System.Windows.Input;
...
public Task ShowWaitCursorWhile(this FrameworkElement element, Action action)
{
   Cursor previousCursor = this.Owner.Cursor;
   element.Cursor = Cursors.Wait;

   return Task.Factory.StartNew(() =>
   {
      action();
   }).ContinueWith((t) =>
   {
      element.Dispatcher.BeginInvoke(new Action(() => element.Cursor = previousCursor));
   });
}
...

		

This seemed like useful code, so I figured I'd throw it out here so that others may benefit from it. Since I am doing Model-View-View Model (MVVM) development, I will probably try to find a way to abstract this into a separate utility that can then be mocked out when testing. That way my view model won't be coupled to the view or its particular cursor/wait implementation.

Print | posted on Thursday, December 1, 2011 1:17 PM

Comments on this post

# re: WPF Asynchronous Wait Cursor using Task Parralel Library

Requesting Gravatar...
You're so cool! I don't think I've read anything like this before.
So good to find somebody with some original thoughts on this subject. Thanks for starting this up.
Left by pesta ultah anak on Mar 24, 2012 3:27 AM

# re: WPF Asynchronous Wait Cursor using Task Parralel Library

Requesting Gravatar...
Pretty great post. I simply stumbled upon your weblog and wanted to say that I have really loved browsing your blog posts.
Left by sandal crocs on Apr 13, 2012 9:37 PM

# re: WPF Asynchronous Wait Cursor using Task Parralel Library

Requesting Gravatar...
Your blog provided us with valuable information to work with. Each every tips of your post are awesome. Thanks dude
Left by fashion grosir on May 16, 2013 11:07 AM

# re: WPF Asynchronous Wait Cursor using Task Parralel Library

Requesting Gravatar...
This will work for simple Actions, but not for concurrent Actions. Consider for a moment what would happen if one were to execute two actions simultaneously.

If we take your un-optimized version and wrap two Tasks then you could potentially end up with a stuck wait cursor. Eg. Task 1, stores the previous window cursor and then changes the cursor to a wait cursor. While Task 1 is still running, Task 2 starts and it stores the wait cursor as the previous cursor. If Task 1 finishes before Task 2, the previous cursor of the window will be restored while Task 2 is still running. Once Task 2 finishes, the wait cursor will once again be shown, because that was the cursor that Task 2 previously saw.

Looking at your optimized version, it seems you've changed things up a bit. ShowWaitCursorWhile takes an element and an Action. However, you're not storing the previous cursor of the element, you're storing the cursor of the Owner of the element. This little change would mostly alleviate the issue mentioned above, assuming the window has an Owner that is. However if two tasks are executing, there's no guarantee that the wait cursor is displayed until both Tasks finish. Whichever Task finishes first will set the cursor to that of the Owner.

Simply put, managing a wait cursor can be hard. Even more so if one starts throwing Tasks into the mix. In order to handle the above situations, a more complex solution is needed and extension methods aren't enough to solve it.
Left by Mr E. on Mar 16, 2015 9:43 PM

Your comment:

 (will show your gravatar)