如何避免System.Media.SoundPlayer在底层音频设备移除时挂起?
解决System.Media.SoundPlayer设备移除导致应用挂起的问题
System.Media.SoundPlayer依赖底层winmm.dll实现,同步播放时遇到音频设备移除会导致线程无限阻塞,且无异常抛出。要解决这个问题,可从以下几个方向入手:
1. 改用异步播放,避免主线程阻塞
同步Play()方法会阻塞当前线程,设备异常时直接卡死线程。改用异步播放可以让主线程保持响应,即使播放线程出问题也不会导致整个应用挂起。
- .NET Core 3.0+ 直接提供
PlayAsync()方法:
var soundPlayer = new SoundPlayer("audio.wav"); await soundPlayer.PlayAsync();
- .NET Framework 可自行封装异步播放方法:
public static Task PlaySoundAsync(this SoundPlayer player) { var tcs = new TaskCompletionSource<bool>(); void PlayCompletedHandler(object sender, EventArgs e) { player.PlayCompleted -= PlayCompletedHandler; tcs.SetResult(true); } player.PlayCompleted += PlayCompletedHandler; player.Play(); return tcs.Task; } // 使用方式 await soundPlayer.PlaySoundAsync();
2. 提前监听音频设备状态变化
不要等设备移除后再处理,提前监听设备事件,在设备移除的第一时间停止播放并释放资源。可以通过Windows的MMDeviceAPI实现,若不想引入第三方库,可使用P/Invoke调用相关API;也可以用NAudio简化开发:
using NAudio.CoreAudioApi; var enumerator = new MMDeviceEnumerator(); enumerator.RegisterEndpointNotificationCallback(new DeviceMonitor(yourSoundPlayerInstance)); // 自定义设备监听类 class DeviceMonitor : IMMNotificationClient { private SoundPlayer _soundPlayer; public DeviceMonitor(SoundPlayer player) { _soundPlayer = player; } public void OnDeviceRemoved(string deviceId) { // 设备移除时立即停止播放并释放 _soundPlayer?.Stop(); _soundPlayer?.Dispose(); } // 实现接口其他方法(空实现即可) public void OnDefaultDeviceChanged(DataFlow flow, Role role, string defaultDeviceId) { } public void OnDeviceAdded(string deviceId) { } public void OnDeviceStateChanged(string deviceId, DeviceState newState) { } public void OnPropertyValueChanged(string deviceId, PropertyKey key) { } }
3. 给播放操作添加超时兜底
即使做了异步和提前监听,仍可能出现极端情况导致播放线程卡住。用超时机制强制终止无响应的播放任务:
var playTask = soundPlayer.PlaySoundAsync(); var timeoutTask = Task.Delay(5000); // 设置5秒超时 var completedTask = await Task.WhenAny(playTask, timeoutTask); if (completedTask == timeoutTask) { // 超时后强制停止并释放资源 soundPlayer.Stop(); soundPlayer.Dispose(); }
4. 替换为更健壮的音频库
System.Media.SoundPlayer本身功能简陋,对异常场景处理不足。可以替换为NAudio、MediaFoundation等专业音频库,这些库对设备异常、资源管理的处理更完善,从根源避免挂起问题。比如用NAudio播放WAV:
using NAudio.Wave; using var waveOut = new WaveOutEvent(); using var waveReader = new WaveFileReader("audio.wav"); waveOut.Init(waveReader); waveOut.Play(); // 设备移除时调用Stop即可,不会出现挂起 waveOut.Stop();
内容的提问来源于stack exchange,提问作者Horst Walter
相关产品推荐
相关产品推荐

