Use BeginInvoke and callbacks to perform tasks asynchronously in C#

[callbacks]

The example Use BeginInvoke and EndInvoke to perform tasks asynchronously in C# explains one way to call a method asynchronously so it can run on a separate core or CPU. In that example, the program calls BeginInvoke several times to start several threads. It then calls EndInvoke to wait for the threads to complete and waits until they are all finished. While it waits, the main UI thread is blocked waiting for the EndInvoke calls to return.

This example uses callbacks instead of waiting for the threads to finish. That lets the UI thread remain responsive while the other threads are working.

The program uses the following code (which is the same as the code used by the previous example) to emboss the images synchronously.

// Emboss the images.
pictureBox1.Image = Emboss(Images[0]);
pictureBox1.Refresh();

pictureBox2.Image = Emboss(Images[1]);
pictureBox2.Refresh();

pictureBox3.Image = Emboss(Images[2]);
pictureBox3.Refresh();

pictureBox4.Image = Emboss(Images[3]);
pictureBox4.Refresh();

The images that the program embosses are stored in the Images array. The code passes each image to the Emboss method and displays the result in a PictureBox.

To emboss the images asynchronously, the program creates a delegate representing the Emboss method. The following code shows the delegate’s declaration. This is the same as the delegate used by the previous example.

// Make a delegate representing the Emboss extension method.
private delegate Bitmap EmbossDelegate(Bitmap bm);

This delegate simply represents a method that takes a Bitmap as a parameter and that returns a Bitmap.

The following code shows the key lines where the program embosses the images asynchronously.

// Copy the images.
Bitmap bm1 = (Bitmap)Images[0].Clone();
Bitmap bm2 = (Bitmap)Images[1].Clone();
Bitmap bm3 = (Bitmap)Images[2].Clone();
Bitmap bm4 = (Bitmap)Images[3].Clone();

// Start the threads.
EmbossDelegate caller = Emboss;
caller.BeginInvoke(Images[0], ImageCallback, pictureBox1);
caller.BeginInvoke(Images[1], ImageCallback, pictureBox2);
caller.BeginInvoke(Images[2], ImageCallback, pictureBox3);
caller.BeginInvoke(Images[3], ImageCallback, pictureBox4);

The program will pass Bitmap objects to asynchronously running threads. Unfortunately if the user interface is using a Bitmap to update a PictureBox while the thread tries to access it, you may get a cryptic error message saying “the object is in use by another process” or something similarly uninformative. To avoid that, the program makes copies of the images and sends the copies to the asynchronous threads.

The program makes a delegate variable named caller that refers to the Emboss method. It then calls the delegate’s BeginInvoke method for each image, passing the method the image to process, the callback method named ImageCallback, and an argument that should be passed to the callbacks when they are invoked. In this case the callbacks are passed the PictureBox objects where they should display the embossed images.

Each call to BeginInvoke starts the Emboss method running in a separate thread. After all of the calls to BeginInvoke, there are five threads running, one for each method call plus the main UI thread. The system distributes the threads on the computer’s cores if possible.

Having launched the new threads, the UI thread continues running its event handler until it finishes and the program continues running its event loop so it can process button clicks, mouse moves, and other Windows messages.

When an asynchronous thread finishes, it calls the ImageCallback method, which was passed into the call to BeginInvoke. That method would ideally display its embossed image on the correct PictureBox but it is running on an asynchronous thread and only the thread that created a control can directly manipulate that control. In this case that means that only the UI thread can safely assign the PictureBox control’s Image property.

To work around this problem, the callback (running on the asynchronous thread) must use the form’s Invoke method to a method in the UI thread to do the work. Yes, it’s confusing! Hopefully seeing the code will help.

The following code shows the ImageCallback method that is called by the thread when it finishes.

// The callback that executes when the asynchronous method finishes.
private void ImageCallback(IAsyncResult ar)
{
    AsyncResult result = ar as AsyncResult;
    EmbossDelegate caller = result.AsyncDelegate as EmbossDelegate;

    // Get the parameter we passed to the callback.
    PictureBox pic = result.AsyncState as PictureBox;

    // Get the method's return value.
    Bitmap bm = caller.EndInvoke(result);

    // Display the picture on the UI thread.
    DisplayPictureDelegate displayer = DisplayPicture;
    this.Invoke(displayer, bm, pic);
}

The callback method receives as a parameter an object that implements the IAsyncResult interface. The code converts that into an AsyncResult object. That object provides a couple of important pieces of information including the delegate that was used to call BeginInvoke and the value used as the final argument to BeginInvoke and that should be passed into the callbacks.

The code uses the result object to get the final argument to BeginInvoke, which in this case is a PictureBox.

Next the code gets the delegate from the result object’s AsyncDelegate property and calls its EndInvoke method to stop the thread and get the Emboss method’s result. IMPORTANT: You must call EndInvoke, even if the callback doesn’t need to do anything with the returned result. EndInvoke lets the program clean up the no longer needed thread to free resources and prevent memory leaks.

Now the program must use Invoke to make another method run in the UI thread. The code creates a DisplayPictureDelegate variable named displayer that refers to the method DisplayPicture. (The delegate and method are described next.) It calls the form’s Invoke method to make it execute the DisplayPicture method in the UI thread, passing it the embossed Bitmap and the PictureBox.

The following code shows the definition of the DisplayPictureDelegate and the DisplayPicture method.

// Display a picture on the UI thread.
private delegate void DisplayPictureDelegate(
    Bitmap bm, PictureBox pic);
private void DisplayPicture(Bitmap bm, PictureBox pic)
{
    // Set the Image property.
    pic.Image = bm;
}

At this point things get a bit more intuitive. The DisplayPicture method is running in the UI thread so it can simply set the PictureBox control’s Image property equal to the Bitmap. (I’ve omitted some bookkeeping code that displays the time to keep things simple. Download the example program to see how it works.)

In one set of tests on my dual-core laptop, creating the embossed images took roughly 2.61 seconds synchronously but only 1.60 seconds asynchronously. Running asynchronously on two cores takes slightly more than half the time needed to run synchronously due to overhead, but it’s still a pretty nice improvement.


Download Example   Follow me on Twitter   RSS feed   Donate




This entry was posted in algorithms, graphics, image processing, threading and tagged , , , , , , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *