(No) fun with threads
Posted by Dan Byström on August 27, 2004
I came across an “interesting” bug the other day. So interesting in fact that I thought it would make a fun quiz. What’s wrong with this piece of example code?
private void Form1_Load(object sender, System.EventArgs e)
{
new System.Threading.Thread( new System.Threading.ThreadStart(myThread) ).Start();
}private void myThread()
{
foreach ( string strFile in System.IO.Directory.GetFiles( @"c:\pictures","*.jpg" ) )
{
if ( pictureBox1.Image!=null )
pictureBox1.Image.Dispose();
pictureBox1.Image = new Bitmap( strFile );
System.Threading.Thread.Sleep( 500 );
}
}
The answer would be that since the code is executed from a another thread, eventually there will come a day when Windows will decide to repaint the pictureBox1 after we have disposed of the picture in it, but before we have replaced it with a new one! This causes a Windows.Forms exception (which can be easily reproduced by dragging another window in front of the PictureBox while the program is running). Very sneaky I thought.
But then it was pointed out to me that instance methods of the PictureBox class aren’t guaranteed to be thread safe and that the whole piece of code is fundamentally wrong anyway! 😦 *sigh* What an embarassment!
So, to do it correctly, here’s how, just to close the case. The pictureBox1.Image property should only be updated from the thread that owns it. Then there is no chance that we can be interrupted before the new picture is in place! Completely straightforward.
private delegate void SafeUpdatePictureBoxDelegate( Bitmap bmpPicture );
private SafeUpdatePictureBoxDelegate m_delegateSafePictureBoxUpdate;private void Form1_Load(object sender, System.EventArgs e)
{
m_delegateSafePictureBoxUpdate = new UppdateraPicBoxDelegate( safePictureBoxUpdate);
new System.Threading.Thread( new System.Threading.ThreadStart(myThread) ).Start();
}
private void safePictureBoxUpdate( Bitmap bmpPicture )
{
if ( pictureBox1.Image!=null )
pictureBox1.Image.Dispose();
pictureBox1.Image = bmpPicture;
}
private void myThread()
{
foreach ( string strFile in System.IO.Directory.GetFiles( @"c:\pictures","*.jpg" ) )
{
pictureBox1.Invoke( m_delegateSafePictureBoxUpdate,
new object[] { new Bitmap(strFile) } );
System.Threading.Thread.Sleep( 500 );
}
}
Obviously you need to catch exceptions as well as a way to stop the thread. That's it. I wanted to demonstrate how tricky it is to correctly work with threads and was trapped myself. Indeed, that is a good demonstration of how tricky it is!!!
Leave a Reply