Coding an app using WPF framework in C# (I'm really really new to it, don't be too hard on my coding style). I want some "goodbye" audio to play (using NAudio 1.8.4 reading a stream from a MPQ file) when I exit and not to be interrupted even if the main window disappeared ; for this reason I use a foreground thread in a class called PlayerThread.
I play sounds using the PlayerThread constructor
foregroundChannel = new PlayerThread(soundStream, null, null);
but I don't keep track of it later, I just rely on the PlayerThread to end "naturally" after playing.
Here is the PlayerThread constructor :
public PlayerThread(MpqFileStream stream, Action<PlayerThread> over, Dispatcher callerDispatcher)
{
noCallback = false;
dispatcher = callerDispatcher;
fadeOut = false;
soundStream = stream;
ThreadStart threadStart = RunThread;
if (callerDispatcher != null)
{
callback = over;
threadStart += () =>
{
dispatcher.Invoke(() =>
{
Cleanup();
});
};
}
this.m_playerThread = new Thread(RunThread)
{
Name = "Audio Player Thread",
IsBackground = false
};
this.m_playerThread.SetApartmentState(ApartmentState.STA);
this.m_playerThread.Start();
}
and here is the work it does :
private void RunThread()
{
internalPlayer = new WaveOut();
Mp3FileReader mp3FileReader = new Mp3FileReader(soundStream);
inputStream = new WaveChannel32(mp3FileReader);
internalPlayer.Init(inputStream);
internalPlayer.Play();
while (internalPlayer.PlaybackState == PlaybackState.Playing && !fadeOut)
{
System.Threading.Thread.Sleep(100);
}
if (fadeOut)
{
while (((float)0F).CompareTo(inputStream.Volume) < 0)
{
System.Threading.Thread.Sleep(150);
inputStream.Volume -= 0.1F;
}
}
internalPlayer.Dispose();
inputStream.Dispose();
mp3FileReader.Dispose();
soundStream.Dispose(); // Yeah I'm so desperate for a solution I'm just Disposing everything even though I don't think it helps
}
When clicking on exit, these lines are executed :
soundEngine.FadeOutBackground();
soundEngine.PlayVoice(SoundNamesConstants.BYE);
soundEngine.WaveGoodbye();
Close();
soundEngine.WaitForTheCrewBeforeWeighingAnchor();
Eventually, here's is the code inside Cleanup() :
if (!noCallback)
callback.Invoke(this);
The callback is pretty irrelevant here, it's something I use to switch between musics with a fade out effect. Plus it doens't occur in this instance as I just set noCallback to false.
If it's any use, here's the resource usage chart. The red line is when I press exit.
UPDATE
Added Thread.Join()
synchronisation as suggest by @sacha :
public void WaitForTheCrewBeforeWeighingAnchor() // yes, every word matters
{
backgroundChannel.Join();
foreach (PlayerThread pt in foregroundChannels)
pt.Join();
}
It has no problem Joining with the backgroundChannel thread but never Joins with the only thread in foregroundChannels. Inverting line 3 with lines 5-6 has the same result, systematically failing to pt.Join();
. This PlayerThread is the one created when clicking on exit.
UPDATE 2 This is not a threading issue. This is only happening because the condition internalPlayer.PlaybackState == PlaybackState.Playing
isn't always met (checked using breakpoints), so we just stay in the while loop. Currently looking for a better way to check playing is over.
RunThread
to see where it gets stuck? – Kevin Gossenocallback = false;
is not actually implemented, should be = true anyway. Use Debug > Windows > Threads to diagnose the deadlock. – Hans Passantnocallback = false;
it is implemented throughsoundEngine.WaveGoodbye();
which sets nocallback to false. It's useful because the callback actually checks if another background music is in the queue (when using backgroundChannel) and we don't want that when exiting. Alas problem persists as all this doesn't fix the while exit problem which happens because the PlaybackState property of WaveOut is not always changed when the sound stops playing (I checked this using breakpoints)... – Cobalt Scales