WPF: Threading basics in WPF

imageIn my previous WPF post I created a simple photo viewer.  If you run the application, you'll notice things "lock up" while downloading an image.  This is because the images are downloaded on the UI thread.  This thread is the same thread that draws updates on the window, so while we are using it no input can be received.  Worse, we can't update the status bar to let the user know we haven't locked up.

For the below code to work, you'll need to add three more namespace declarations to the original file:

using System.Net;
using System.IO;
using System.Windows.Threading;

The first thing to change is the TreeView's SelectedItemChanged event, so it fires off a thread:

        String selectedLink = String.Empty;
void tvPhotos_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) { TreeViewItem item = e.NewValue as TreeViewItem; if (item != null) { String link = item.Tag as String; if (link != null) { selectedLink = link; sbiMessage.Content = "Loading: " + link; LoadImageHandler limg = new LoadImageHandler(LoadImage); limg.BeginInvoke(link, null, null); } } }

This version uses LoadImageHandler to launch another thread and sets a message in the status bar that we loading the image.  BeginInvoke returns immediately after launching the thread, allowing the UI updates to process (status bar and highlighting the selected node in the TreeView).  I've added a string to the class, selectedLink, and set it to the link that is loading - more on why in a moment.  Let's look at LoadImageHandler and LoadImage:

        delegate void LoadImageHandler(String link);
        void LoadImage(String link) {
            Uri uri = new Uri(link);
            WebClient web = new WebClient();
            Stream imgData = web.OpenRead(uri);
            MemoryStream memory = new MemoryStream();

            int data = imgData.ReadByte();
            while (data != -1) {
                memory.WriteByte((byte)data);
                data = imgData.ReadByte();
            }
            memory.Seek(0, SeekOrigin.Begin);
            
            BitmapImage bi = new BitmapImage();
            bi.BeginInit();
            bi.StreamSource = memory;
            bi.EndInit();
            bi.Freeze();

            UpdateImageUIHandler upUI = new UpdateImageUIHandler(UpdateImageUI);
            object[] args = { link };
            this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, upUI, bi, args);

            web.Dispose();
        }

LoadImage is the actual method run, LoadImageHandler is a delegate used to run the method on a separate thread.  The first half is straight forward, a call is made on the web to the image, and it's downloaded to a MemoryStream.  This MemoryStream is used as the source for the BitmapImage that will be loaded into our Image control.  The interesting call here is Freeze() - normally you cannot create UI objects on one thread and use them in another.  Classes that descend from Freezable are the exception - when you call Freeze on a Freezable you are saying "I swear under penalty of Exceptions I will make no changes to this object."

It is worth note a Freezable cannot not always be frozen.  Check the CanFreeze property to see if you can freeze the object.  In writing this example I learned a BitmapImage that uses a Uri instead of a Stream cannot be frozen; hence the code to copy the image to a MemoryStream.

Last, the method create an UpdateImageUIHandler to update the UI with the loaded BitmapImage.  The special magic here is the Window.Dispatcher - almost all classes in WPF have a Dispatcher which is a convenient property to cause action to take place on the thread the object was created on.  Notice the call to web.Dispose() comes after the call to BeginInvoke - just as before BeginInvoke returns immediately so we can dispose of the WebClient while the UI thread is updating.   

        public delegate void UpdateImageUIHandler(BitmapImage imgData, String link);
        public void UpdateImageUI(BitmapImage imgData, String link) {
            if (link.Equals(selectedLink)) {
                imgDisplayed.Source = imgData;
                sbiMessage.Content = link;
            }
        }

Not much left to do but update the Image control, but here is the use of selectedLink.  If, while we were downloading one image, the user selected another node, we might load the wrong image into view.  By checking the user is on the same node as when we started we can avoid this problem.  Sadly, when using delegates there isn't a way to stop or kill the background thread that is downloading an old image - for that we would need to use a Thread class, and that's beyond the scope of this post.

Posted By Mike On Thursday, August 09, 2007
Filed under wpf threading | Comments (2)

Submit this story to DotNetKicks   

Arif - Monday, October 19, 2009 5:13:46 PM

I guess U shud look at some basic concepts of Invoke and BeginInvoke.
Check description of these function please. Both of these functions run on the Main UI thread itself. None will span another thread.
Now wat is the difference: The beginInvoke creates a function pointer and places the call on the CPU execute Queue and the process immediate lines after it.When the main thread is free, the cpu picks the function pointer of this beginInvoke and executes the function.
Invoke, calls the function immediately.

U have included System.windows.Threading but where have U used it.
My dear fellow developer, Threading is a deep and complicated concept, please dont confuse ppl

Michael C. Neel - Monday, October 19, 2009 7:59:14 PM

There is so much fail in the above comment, I'll just link to the documentation on asynchronous delegates:

http://msdn.microsoft.com/en-us/library/22t547yb.aspx

Leave a comment



Your name:
 

Your email (not shown):
 
Will display your Gravatar image.

Your website (optional):



About Me

Michael C. Neel, born 1976 in Houston, TX and now live in Knoxvile, TN. Software developer, currently .Net focused. Board member and President of ETNUG, and organizes CodeStock, East Tennessee's annual developers conference. .Net speaker, a Microsoft ASP.NET MVP and ASPInsider. Founder of FuncWorks, LLC and Feel The Func podcast.

Proud father of two amazing girls, Rachel and Hannah.

 Subscribe to ViNull.com |  Comments

Follow me on Twitter | Contact Me

Related Posts

SilverLight 2.0: Setting the Background of a Button

I know, this hardly seems a topic worthy of a blog post.  To create a Button in SilverLight like the first in the picture is just: <Button Content="Stock ... Read more

Code Camp Room Planner

I just finished creating a project on CodePlex for the Code Camp Room Planner, a little app I wrote to help me plan the schedule at CodeStock 2009.  ... Read more

WPF: Using Viewbox and Canvas to create a virtual resolution workspace

First, for you SilverLight 2.0 junkies, you can skip over this.  SilverLight 2.0 doesn't (yet?) support the WPF ViewBox. The problem: when you start ... Read more

WPF: Dive into WPF

For this first post on WPF I've decided it would be best to start off with a "real" application.  This means we'll need those thing almost all applications ... Read more

Speaking in Maryland this weekend

I've just been confirmed as a last minute addition to the Central Maryland Spring Code Camp '08.  I'll be speaking on WPF with my From Zero to XAML ... Read more

FeelTheFunc Podcast

CodeStock
Are you going?

ASPInsiders Member

ETNUG Member