I am using the Wasapi provider and in __WasapiOut.cs__, the implementation of __Stop()__ has a subtle issue in it. With rapid calls to __Stop()__/__Play()__ back to back (actually, __Play()__ and then __Stop()__ and then __Play()__ again when the __PlaybackStopped__ event is raised), a null reference exception can happen in __Stop()__ where __playThread.Join()__ is called.
A small race can exist where playThread is null when a call is made into this method, causing a null reference exception.. If execution resumes, the thread continues to pump data and audio will continue to play.
Adding an __object__ member to the class and using it as a __lock__ for the entire code in __Stop()__ and also in __Play()__ resolves the issue. Also, I would suggest using an event to get the worker/play thread to die. This would allow the thread to fall out of the __WaitAny(...)__ call faster allowing for more timely cleanup.
Added to the top of the class:
```
protected object _locker = new object();
```
__Play()__ and __Stop()__ methods:
```
/// <summary>
/// Begin Playback
/// </summary>
public void Play()
{
lock( _locker )
{
if( playbackState != PlaybackState.Playing )
{
if( playbackState == PlaybackState.Stopped )
{
playThread = new Thread( new ThreadStart( PlayThread ) );
playbackState = PlaybackState.Playing;
playThread.Start();
}
else
{
playbackState = PlaybackState.Playing;
}
}
}
}
/// <summary>
/// Stop playback and flush buffers
/// </summary>
public void Stop()
{
lock( _locker )
{
if( playbackState != PlaybackState.Stopped )
{
playbackState = PlaybackState.Stopped;
playThread.Join();
playThread = null;
}
}
}
```
A small race can exist where playThread is null when a call is made into this method, causing a null reference exception.. If execution resumes, the thread continues to pump data and audio will continue to play.
Adding an __object__ member to the class and using it as a __lock__ for the entire code in __Stop()__ and also in __Play()__ resolves the issue. Also, I would suggest using an event to get the worker/play thread to die. This would allow the thread to fall out of the __WaitAny(...)__ call faster allowing for more timely cleanup.
Added to the top of the class:
```
protected object _locker = new object();
```
__Play()__ and __Stop()__ methods:
```
/// <summary>
/// Begin Playback
/// </summary>
public void Play()
{
lock( _locker )
{
if( playbackState != PlaybackState.Playing )
{
if( playbackState == PlaybackState.Stopped )
{
playThread = new Thread( new ThreadStart( PlayThread ) );
playbackState = PlaybackState.Playing;
playThread.Start();
}
else
{
playbackState = PlaybackState.Playing;
}
}
}
}
/// <summary>
/// Stop playback and flush buffers
/// </summary>
public void Stop()
{
lock( _locker )
{
if( playbackState != PlaybackState.Stopped )
{
playbackState = PlaybackState.Stopped;
playThread.Join();
playThread = null;
}
}
}
```