views:

1536

answers:

2

Here's a deceptively simple question:

What is the proper way to asynchronously play an embedded .wav resource file in Windows Forms?

Attempt #1:

var player = new SoundPlayer();
player.Stream = Resources.ResourceManager.GetStream("mySound");
player.Play(); // Note that Play is asynchronous
  • Good: doesn't block the UI thread
  • Bad: SoundPlayer and the embedded resource stream are not immediately disposed.

Attempt #2:

using (var audioMemory = Resources.ResourceManager.GetStream("mySound"))
{
    using (var player = new SoundPlayer(audioMemory))
    {
        player.Play();
    }
}
  • Good: UI thread is not blocked, SoundPlayer and audio memory stream are immediately disposed.
  • Bad: Race condition! Play() is async, and if audio memory gets disposed before Play is done...boom! Runtime exception is thrown.

Attempt #3:

using (var audioMemory = Resources.ResourceManager.GetStream("mySound"))
{
    using (var player = new SoundPlayer(audioMemory))
    {
        player.PlaySync();
    }
}
  • Good: Player and audio stream are immediately disposed.
  • Bad: PlaySync blocks the UI thread

Attempt #4:

ThreadPool.QueueUserWorkItem(ignoredState =>
  {
    using (var audioMemory = Resources.ResourceManager.GetStream("mySound"))
    {
        using (var player = new SoundPlayer(audioMemory))
        {
            player.PlaySync();
        }
    }
  });
  • Good: UI doesn't freeze, player and memory stream are immediately disposed.
  • Bad: Because this fires often, we may run out of thread pool threads! See Larry Osterman's what's wrong with this code part 26.

It seems like SoundPlayer should have a PlayAsyncCompleted event. Unfortunately, no such event exists. Am I missing something? What's the proper way to asynchronously play a .wav embedded resource in Windows Forms?

+1  A: 

I still use the good ol' waveOut____ functions from the win32 API. Here's a good code sample:

http://www.codeproject.com/KB/audio-video/cswavplay.aspx

Edit: a much simpler solution to your problem is to extract the embedded resource, save it as a real file somewhere, and then use SoundPlayer to play the file. A little clunky, but simple and you won't have the resource disposal problem.

MusiGenesis
With waveOut, can you play a sound asynchronously and dispose of the audio stream when finished?
Judah Himango
Sure. The code in the link does exactly that (unless it doesn't - I may have given you the wrong link). waveOut____ is the old-time API that you can really do whatever you like with. Relatively over-complicated, but powerful.
MusiGenesis
+4  A: 

I have don't enough reputation to comment so I'll just answer.

If your requirements to play sound are "deceptively simple" (you just want to play the occasional sound when a single winform user does something) then I would use Attempt #4 above.

Larry Osterman's "what's wrong with this code part 26" has his "system" spin off a new threadpool thread (to play sound) with each keystroke. He indicates than hammering away on it saturated the default 500 thread pool size in about 15 seconds of typing but this was also with a client/server app using async RPC that were also using the threadpool. Really not a "deceptively simple" application.

If you are trying to queue sound bites every second (or faster) for 10s or 100 of seconds at a time then its really not a "simple application" and a queued threading/priority subsystem would probably be in order.

BlueShepherd
Thanks for the feedback. My app is a client/server app with async RPC, with a sound being played when a remote user sends in some message. It's exactly the kind of problem Osterman describes, unfortunately. My app is not simple, however, I do think "asynchronously playing a sound file from an embedded resource" is a simple scenario.
Judah Himango
I agree that "asynchronously playing a sound file from an embedded resource" is a simple scenario but your requirement to play a sound when a remote user sends a message (given that you can get tons of messages) is not ... so If the sound is already playing and a new message arrives do you want to (a) ignore the message cause the sound is already playing? If so, don't use the threadpool, just use a named thread and if the thread is already running, don't do anything. if the thread isn't running, start it up.
BlueShepherd
(b) queue the "SAME" sound to play again? if so, every time you get a message, increment a shared variable (or, if zero, start the thread). in the threadpool thread, every time you play the message, decrement the variable in loop.(c) queue a different sound? You will have to build a queue to "hold" the sounds to be played. Put sounds on the queue when you get a message, strip them off when you play them
BlueShepherd
Thanks for your suggestions. I've marked yours as the answer.
Judah Himango