Archive

Posts Tagged ‘cross thread exceptions’

And finally a bit of masking tape.

February 27th, 2008 Bobby Alexander No comments

As promised, today we will look at a scenario which uses threads, locks and invokes that can lead to hard to find deadlocks.

Scenario: You have an application which has a treeview control. Let’s call it myFamilyTreeView . The tree will list, you guessed it, you family tree all the way from Adam. (uh well kind of)

Next, we have a global (static) image list, familyTreeImageList, which will be populated with pictures of your nagging aunts, uncles and everyone in between. The filename of each picture used to populate the imagelist is a unique id such as P11.jpg, P45.jpg etc. We then associate this imagelist to the treeview, like this:

myFamilyTreeView.ImageList = familyTreeImageList ;

The application works like this. You have a database containing a list of names and a primary key corresponding to the his or her picture’s filename.

So uncle John, who still runs a chicken farm in Texas at the age of 70, has a primary key of 36. So his picture will stored in a file named P36.jpg.

So far so good? Great.

The program goes through the database table and adds each family member to myFamilyTreeView. When you add a node to the treeview, you can specify the index of the image in the imagelist corresponding to the person’s picture which will be displayed as an icon next to the persons name.

Now, since you may have hundreds (!) of relatives, this process may take some time. And since you just finished reading my other articles, you decide to do the treeview population in a different thread.

So you write a thread handler for your worker thread that looks something like this:

void PopulateTrees()
{

//loop through the database entries
{


//id is the primary key of the database item
int photoIndex = GetImageIndex(id);

//and finally
//name: Name of the person
if (InvokeRequired)
this.Invoke(AddNodeDelegate,new object[]{name, photoIndex});
else
AddNode(name, photoIndex);
}

}

//Method wrapped by the AddNodeDelegate
void AddNode(string name, int photoIndex)
{
myFamilyTreeView.Nodes.Add(new TreeNode(name, photoIndex, photoIndex));
}

//Static method to operate on the static (global) familyTreeImageList
static int GetImageIndex(int id)
{
//Get photo from id
//if id = 36, file name will be P36.jpg
//Load the file into a System.Drawing.Image, say, someonesImage

//and finally
familyTreeImageList.Images.Add(someonesImage);
}

If you had read the last edition of Code Widgets, you would know that
since familyTreeImageList is not a control, you can modify it from another thread. That means GetImageIndex can be called from a worker thread. Right?

Well, right …. and wrong. There is a very unique (and interesting) catch in this scenario. Remember that we had associated the imageList to the treeview. So when you add an image to the imagelist, it will in turn cause the treeview to be updated (because of the association). So it is as if you are updating the treeview from a different thread, albeit, indirectly. So the GetImageIndex method will throw the dreaded cross-thread exception.

Quite a revelation wasn’t it?

The fix is as easy as using invoke. So we we rewrite the method as follows:

int GetImageIndex(int id)
{
//Get photo from id
//if id = 36, file name will be P36.jpg
//Load the file into a System.Drawing.Image, say, someonesImage

//and finally
if (InvokeRequired)
this.Invoke( AddImageToListDelegate, new object[]{someonesImage});
else
AddImageToList(someonesImage);

}

int AddImageToList(System.Drawing.Image someonesImage )
{
familyTreeImageList.Images.Add(someonesImage);
}

That was lesson number one.

Now let us complicate things a bit. Let us suppose that the database you are using is being updated continuously as you are populating the treeview. Probably you have set up a website somewhere where your relatives can login and change their pictures.

As soon as someone updates their details, your treeview will have to reflect that change. For this purpose you have a thread that is running continuously looking for changes. The moment it detects a change, it pushes the change notification information into a queue. Another thread picks up these notifications from the queue and makes updates to the treeview according to the notification data.

//This is the thread handler which processes notifications
void ProcessUpdateNotifications()
{
….
….
//extract the name of the person whose data was modified, from the notification data

//UpdateNodeDelegate wraps UpdateNode method

this.Invoke(UpdateNodeDelegate,new object[]{name});

}

void UpdateNode(string name)
{
//find the node index corresponding to the name
//lets call it nameIndex
//update the image with the new index

int newIndex = GetImageIndex(name);
myFamilyTreeView.Nodes[nameIndex].ImageIndex = newIndex;
}

You will notice that now there are there are two threads trying to access GetImageIndex(). One is the thread populating the treeview and the other is the one doing the update To ensure data consistency we will add a lock to the method. The uninitiated will add a lock as follows:

int GetImageIndex(int id)
{
lock(this)
{
//Get photo from id
//if id = 36, file name will be P36.jpg
//Load the file into a System.Drawing.Image, say, someonesImage

//and finally
if (InvokeRequired)
this.Invoke( AddImageToListDelegate, new object[]{someonesImage});
else
AddImageToList(someonesImage);
}
}

Remember my earlier article warning you of obtaining a lock on this? Now we will see why. The above method will cause a deadlock. You didn’t see that coming did you? The reason why this happens is as follow:
A thread enters the method and obtains a lock on this. When it reaches the invoke method, application tries to switch to the UI thread to execute AddImageToListDelegate. But the UI thread is already locked by the worker thread. To switch to the UI thread, it has to exit the block. But to exit the block, the invoke method has to be executed. The lock and invoke are waiting for each other. To put it simply ladies and gentlemen, we have a deadlock.

This is lesson number two.

To fix this, we use a static object instead to lock on to. Refer my previous article for more details.

int GetImageIndex(int id)
{
lock(lockObject)
{
//Get photo from id
//if id = 36, file name will be P36.jpg
//Load the file into a System.Drawing.Image, say, someonesImage

//and finally
if (InvokeRequired)
this.Invoke( AddImageToListDelegate, new object[]{someonesImage});
else
AddImageToList(someonesImage);
}
}

The static lockobject ensures that only one thread enters the critical section at a time and guess what? No more deadlock.

Well, those are all the lessons we have time for in this edition of Code Widgets. Stay tuned for more revelations

Code Safely
Alex

Categories: Uncategorized Tags: , , , , , , ,