Use BeginInvoke and EndInvoke to perform tasks asynchronously in C#

This example uses the Emboss method to create embossed images. How that method works isn't important to the discussion of working asynchronously so it isn't covered here. Download the example to see how it works. The only thing about that method that matters for this discussion is that it is slow so running it on multiple cores or CPUs can save time.

The program uses the following code 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 must first make a delegate representing the Emboss method. The following code shows the delegate's declaration.

// 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 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;
IAsyncResult result1 = caller.BeginInvoke(Images[0], null, null);
IAsyncResult result2 = caller.BeginInvoke(Images[1], null, null);
IAsyncResult result3 = caller.BeginInvoke(Images[2], null, null);
IAsyncResult result4 = caller.BeginInvoke(Images[3], null, null);

// Wait for the threads to complete.
pictureBox1.Image = caller.EndInvoke(result1);
pictureBox2.Image = caller.EndInvoke(result2);
pictureBox3.Image = caller.EndInvoke(result3);
pictureBox4.Image = caller.EndInvoke(result4);

The program will pass Bitmaps 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 informative. 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 caller's BeginInvoke method for each image, passing the images as the first argument to BeginInvoke. The second and third arguments are for a callback method to be called when each thread finishes. This example doesn't use callbacks so it passes null for those arguments.

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 will distribute the threads on the computer's cores if possible. The main program should continue doing as much as it can but eventually it needs to wait for the other threads to complete. To do that, it calls the caller's EndInvoke method passing it the IAsyncResult object it received when it called BeginInvoke. The IAsyncResult object lets EndInvoke know for which thread to wait.

It doesn't matter in what order the program waits for the threads because it needs to wait for all of them. The only important thing here is that it starts all of the threads before it waits for any of them.

Note also that only the UI thread, the user-interface thread that created the form's controls, can interact directly with the form's controls. That means, for example, that the Emboss method cannot set a PictureBox's Image property. You can use the Form's Invoke method to work around that problem if necessary but this example doesn't need to because it's the main UI thread that eventually sets the PictureBoxes' Image properties after the calls to EndInvoke return.

In one set of tests on my dual-core laptop, creating the embossed images took roughly 4.66 seconds synchronously but only 2.73 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.

   

 

What did you think of this article?




Trackbacks
  • No trackbacks exist for this post.
Comments
  • No comments exist for this post.
Leave a comment

Submitted comments are subject to moderation before being displayed.

 Name

 Email (will not be published)

 Website

Your comment is 0 characters limited to 3000 characters.