You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MAUI中如何利用IDisposable在AVAudioPlayer播放完成后释放资源

MAUI中AVAudioPlayer播放完成后自动释放资源的解决方案

问题根源

  • 使用using语句时,代码块执行完毕会立即调用Dispose,而AVAudioPlayer.Play()是异步执行的,此时音频还未开始播放就被销毁,导致无声音输出。
  • 不使用using时,实例由GC管理,但如果AVAudioPlayer的回调事件持有实例引用,GC无法及时回收,析构函数迟迟不执行,资源无法释放。

方案1:通过播放完成回调自动触发释放

修改VoicePlayer类,订阅AVAudioPlayer的FinishedPlaying事件,在播放完成时主动调用Dispose,实现资源自动释放。

代码实现

public class VoicePlayer : IVoicePlayer, IDisposable
{
    private AVAudioPlayer? _audioPlayer;
    private bool _disposed = false;

    public void Initialize(string audioFilePath)
    {
        var audioUrl = NSUrl.FromFilename(audioFilePath);
        _audioPlayer = AVAudioPlayer.FromUrl(audioUrl);
        // 订阅播放完成事件
        _audioPlayer.FinishedPlaying += AudioPlaybackFinished;
        _audioPlayer.PrepareToPlay();
    }

    public void Play()
    {
        _audioPlayer?.Play();
    }

    private void AudioPlaybackFinished(object? sender, AVStatusEventArgs e)
    {
        // 播放完成后立即释放资源
        Dispose();
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;

        if (disposing)
        {
            // 清理托管资源:移除事件订阅、销毁AVAudioPlayer
            if (_audioPlayer != null)
            {
                _audioPlayer.FinishedPlaying -= AudioPlaybackFinished;
                _audioPlayer.Stop();
                _audioPlayer.Dispose();
                _audioPlayer = null;
            }
        }

        // 清理非托管资源(如有)
        _disposed = true;
    }

    ~VoicePlayer()
    {
        Dispose(false);
    }
}

使用方式

无需using,直接创建实例调用播放方法,播放完成后自动释放:

var voicePlayer = new VoicePlayer();
voicePlayer.Initialize("Resources/Audio/notification.mp3");
voicePlayer.Play();

方案2:异步播放+using语句(可控性更强)

封装异步播放方法,等待播放完成后再执行using的释放逻辑,既保证资源自动释放,又能同步控制播放流程。

代码实现

public class VoicePlayer : IVoicePlayer, IDisposable
{
    private AVAudioPlayer? _audioPlayer;
    private bool _disposed = false;
    private TaskCompletionSource<bool>? _playCompletionSource;

    public void Initialize(string audioFilePath)
    {
        var audioUrl = NSUrl.FromFilename(audioFilePath);
        _audioPlayer = AVAudioPlayer.FromUrl(audioUrl);
        _audioPlayer.FinishedPlaying += AudioPlaybackFinished;
        _audioPlayer.PrepareToPlay();
    }

    public async Task PlayAsync()
    {
        _playCompletionSource = new TaskCompletionSource<bool>();
        _audioPlayer?.Play();
        // 等待播放完成
        await _playCompletionSource.Task;
    }

    private void AudioPlaybackFinished(object? sender, AVStatusEventArgs e)
    {
        _playCompletionSource?.TrySetResult(e.Succeeded);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;

        if (disposing)
        {
            if (_audioPlayer != null)
            {
                _audioPlayer.FinishedPlaying -= AudioPlaybackFinished;
                _audioPlayer.Stop();
                _audioPlayer.Dispose();
                _audioPlayer = null;
            }
            _playCompletionSource?.TrySetCanceled();
        }

        _disposed = true;
    }

    ~VoicePlayer()
    {
        Dispose(false);
    }
}

使用方式

通过await配合using,确保播放完成后自动释放:

using var voicePlayer = new VoicePlayer();
voicePlayer.Initialize("Resources/Audio/notification.mp3");
await voicePlayer.PlayAsync();
// 播放完成后自动执行Dispose

核心注意事项

  • 必须在Dispose中移除FinishedPlaying事件订阅,避免因事件引用导致的内存泄漏。
  • 严格遵循IDisposable标准实现模式,区分托管/非托管资源的释放逻辑。
  • 若需支持中途停止播放,需在Stop方法中完成_playCompletionSource的状态设置,避免异步任务挂起。

内容的提问来源于stack exchange,提问作者Reza

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 17:43:15