Using a BackgroundWorker to load SoundEffects into memory

In a previous post I showed you how to use specific XNA Framework functionality inside a Silverlight application for Windows Phone 7 to be able to use sound effects. Typically, when you want to play a sound effect you want to have it available immediately. As long as you don’t have too many sound effects to be played inside your application you can keep them all available in memory. However, you still need to load the sounds into memory at some time, typically when the application is started. Even if you don’t have too many different sounds that you want to use inside your application, loading sounds from a file into memory is still a time consuming operation, something that especially hurts your application during startup. After all, the end user that starts your application wants to work with it immediately, and it is a frustrating experience if you have to wait a while, even if it is only for a few additional seconds. The obvious choice in this situation is to load the sounds in the background, allowing the user to already working with the application. Of course I am using the sounds I am loading in the background as an example to show a BackgroundWorker in action. More long lasting operations (and not only during application initialization) are candidates to run on a BackgroundWorker. In a later blog post we will talk more about performance of Silverlight based Windows Phone 7 applications and things you have to think about. This post simply shows you how to use the BackgroundWorker. To talk about the BackgroundWorker we will take a look at the same application we used before when discussion the use of sounds inside an application. To separate sounds and sound handling from the rest of my application, I created a SoundPlayer class with a place holder for a number of sounds and a static method that loads those sounds from a number of wav files into SoundEffect objects:

  1. public static void LoadSoundEffects()
  2. {
  3.     soundLoader = new BackgroundWorker();
  4.     soundLoader.DoWork += new DoWorkEventHandler(soundLoader_DoWork);
  5.     soundLoader.RunWorkerCompleted +=
  6.         new RunWorkerCompletedEventHandler(soundLoader_RunWorkerCompleted);
  8.     soundLoader.RunWorkerAsync();
  9. }

In the above code snippet, a new instance of a BackgroundWorker is created and two event handlers are assigned to respectively the DoWork event and the RunWorkerCompleted event. Finally, the RunWorkerAsync method is called, which in turn fires the DoWork event on the BackgroundWorker, allowing the event handler to execute on a separate thread. You have to be careful not to update UI Elements inside the DoWork event hander. It is only allowed to update UI Elements on the thread that created them (typically the UI Thread). In this sample this is no problem since the BackgroundWorker is only used to load sounds into memory. If you need to update UI Elements on a separate thread you must make use of the BeginInvoke method of the Dispatcher class.

In this sample, the DoWork even handler is simple and straight forward. It looks like this:

  1. static void soundLoader_DoWork(object sender, DoWorkEventArgs e)
  2. {
  3.     int i;
  5.     soundEffects = new SoundEffect[nrSoundEffects];
  7.     for (i = 0; i < nrSoundEffects; i++)
  8.     {
  9.         Stream audioStream = TitleContainer.OpenStream(sounds[i]);
  10.         soundEffects[i] = SoundEffect.FromStream(audioStream);
  11.         audioStream.Close();
  12.     }
  13. }

We are simply reading audio information through a number of streams and store them locally. In order to read the audio files, they are defined as content in the Visual Studio project. When all sounds are stored (or in other words when we are leaving the DoWork method), the BackgroundWorker raises the RunWorkerCompleted event. In its event handler, for this example, we are simply unsubscribing from all events, because the BackgroundWorker to load sounds is only called one time:

  1. static void soundLoader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
  2. {
  3.     if (soundLoader != null)
  4.     {
  5.         soundLoader.DoWork -= soundLoader_DoWork;
  6.         soundLoader.RunWorkerCompleted -= soundLoader_RunWorkerCompleted;
  7.         soundLoader = null;
  8.     }
  9. }

The BackgroundWorker has more functionality, for instance to report progress for long lasting operations. Progress information can be easily displayed to the user. In the above shown example, progress information is not used. The end result of using a BackgroundWorker is that the user interface remains responsive. In this particular example, the application starts pretty fast so that end-users can start playing while sounds are still being loaded.

If you are curious about this game, you can find it Clicker here on the Windows Phone 7 Marketplace. Note: This link will work from a Windows Phone 7, or from a PC with Zune software installed. The game has a trial mode so you can try it for free.