Using the ThreadPool to Run Animated Gifs in C# and .NET


Figure 1 - Animated Gifs running in a ThreadPool

One of the nice features incorporated into the .NET framework is the ThreadPool class.  If you are not in the mood to manage your threads (determining when they sleep and when they wake up),  you can take advantage of this powerful class consisting of some easy to use static functions.  In this article we will discuss the use of the ThreadPool class in conjunction with the ImageAnimator class to run 3 animated gifs in separate threads.

The great thing about the ThreadPool class is you dont really have to deal with threads directly. There are two main methods used in this example from the ThreadPool class, each one is described below. 

ThreadPool Static Methods Description
RegisterWaitForSingleObject Used to trigger an Asynchronous callback with an event, if the event doesnt occur in the time out period, it triggers anyway.
QueueUserWorkItem Used to put a new worker thread in the thread pool along with an associated object.

Table 1 - ThreadPool class methods

ThreadPools have an added advantage over the thread class in that, not only do they allow you to create a thread, but also it lets you pass an object associated with the thread to the thread delegate.  I havent figured out how to do this with the Thread class yet.  Perhaps in the future, the .NET framework will be more accommodating with this task.

The object you pass into your delegate can be just about anything since it can box the instance of your class.  When you call QueueUserWorkItem, you pass in the delegate for your thread as the first parameter, and the associated object as your second parameter.

RegisterWaitForSingleObject has a different purpose.  It allows you to trigger a callback function with a WaitHandle or AutoResetEvent.  The WaitHandle lets you do things like trigger the callback when all of your threads are complete or any of your threads are complete (it depends upon how you set it up).  The AutoResetEvent can be triggered programmatically by setting it to signal that the event occurred.  RegisterWaitForSingleObject also allows you to specify a timeout period, so that if no event is triggered by a WaitHandle or AutoResetEvent, the callback will be called automatically after the timeout period has expired.

Now we are ready to look at our example code.  The RegisterWaitForSingleObject call and QueueUserWorkItems are called initially to set up the threads and callbacks.  The first time you call QueueUserWorkItems, the new thread pool is created and also the first thread is created and placed in the thread pool queue.  Below is the sample initialization code for our form full of gif animations.

Listing 1 - Initializing the thread pool

public void TestAnimations()
{
// construct AutoResetEvent for signaling Hi Button
// Appearance on the form
ev = new AutoResetEvent(false);
button1.Enabled = true;
// Set up asynchronous callback trigger using
// the AutoResetEvent and a 20 second timeout period
ThreadPool.RegisterWaitForSingleObject(ev,new WaitOrTimerCallback(WaitThreadFunc),
"Hi",20000,false); 
// put each thread into the pool for running
// each animated gif. Pass the name of the gif
// file into the corresponding thread
string[] GifFile = {"gif1.gif", "gif2.gif","gif3.gif"};
for (int i = 0; i < 3; i++)
{
ThreadPool.QueueUserWorkItem(newWaitCallback(this.RunAnimation),(object)GifFile[i]);
}
}

The RunAnimation thread Delegate prepares our GIF file for animation using the ImageAnimator class.  The ImageAnimator class has a series of static functions that lets us play our gif file.  Below are the methods used for animating the gif: (Use of  the ImageAnimator can be found in The Complete Visual C# Programmers Guide in the GDI+ section at the end of chapter 15).

ImageAnimator Static Methods Description
Animate Sets up the image and event handler for updating the frame of the animated gif
UpdateFrames Move to the next GIF

Table 2 - ImageAnimator Class Methods

The RunAnimation thread delegate is shown below.  From the code, you can see the initial call to Animate.  The image is created from the object passed into the thread which is unboxed to get the name of the image.  This string is also used to locate the array position of the image because the file names are conveniently named gif1, gif2, and gif3 in this example:

Listing 2 - Initial code for starting the Animated Gif file

void RunAnimation(object filename)
{
// Image is created from the filename
Image theImage = Image.FromFile((string)filename);
// the image is set up for animation using the
// ImageAnimator class and an event handler
ImageAnimator.Animate(theImage, newEventHandler(this.OnFrameChanged)); 
// we also need to parse the image name to
// determine the position of the gif in the array
string name = (string)filename;
int periodPosition = name.LastIndexOf(".");
periodPosition--; // get number
int index =Convert.ToInt32(name[periodPosition].ToString());
Gifs[index-1] = theImage;
}

OnFrameChanged is called each time the UpdateFrame is called for the image.  Inside OnFrameChanged we call invalidate to force a repaint of the gif and a subsequent call to UpdateFrame:

Listing 3 - Event Handler for UpdateFrame event of the Animated Gif

private void OnFrameChanged(object o, EventArgs e)
{
this.Invalidate();// force a repaint of the gif
}

All of the painting of the Gifs are handled in the OnPaint event.  Each thread will trigger the OnPaint event through the OnFrameChanged Event which is also contained in the individual threads.  The Paint Event Handler, shown below,  does the painting of the images for all three threads:   

Listing 4 - Painting the Gif Images on the Form

private void Form1_Paint(object sender,System.Windows.Forms.PaintEventArgs e)
{
//Get the next frame ready for rendering for the image in this thread.
ImageAnimator.UpdateFrames();
Graphics g = e.Graphics;
//Draw the current frame in the animation for all of the images
for (int i = 0; i < 3; i++)
{
if (Gifs[i] != null)
g.DrawImage(Gifs[i], (i%2)*150, (i/2)*150, Gifs[i].Width, Gifs[i].Height);
}
// if AutoReset event was triggered, paint the red circle with the HI
if (AllowHi)
{
g.FillEllipse(Brushes.Red, 200, 175, 50, 50);
g.DrawString(CircleString, TheFont, Brushes.Black,215, 190, new StringFormat());
}

Youll also note in the listing above the AllowHi boolean flag.  This flag is set in the callback after WaitForSingleObject callback is triggered (either by setting the signal of the AutoResetEvent or by timing out after 20 seconds).  If you click on the Trigger Hi button on the form, it signals the AutoResetEvent  (by calling Set) which in turn triggers the WaitForSingleObject callback.

The callback delegate passed by  the WaitForSingleObject method  of the ThreadPool is shown below:

void WaitThreadFunc(object test, bool b)
{
AllowHi = true;
CircleString = (string)test;
}

Notice that you can also pass an object into the callback from the WaitForSingleObject function of the ThreadPool.  In this case a string with the message displayed in the red circle is passed into the method.

The behavior the combined code exhibits when running the example is the following. 

  1. If the button is pressed,  the red circle with the Hi  pops up immediately.
  2. If the button is not pressed, the red circle with the Hi will show up anyway after 20 seconds (the timeout period).

Conclusion

Thread pools are a no-brainer way to implement thread management and the ThreadPool class gives us the tools we need to create and manage threads.   The gifs were brought to you courtesy of a cool artist site I found while searching for animated gifs to use to create this article.  The next step, you may have guessed,  is to thread the animated gifs in ASP.NET.

References


Similar Articles