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

.NET MAUI中Timer二次调用时Task.Delay空引用异常解决问询

.NET 7 MAUI安卓定时拍照二次调用异常解决方案

核心问题分析

  1. 定时器状态污染:复用TimerTrigger实例时,未清理之前的定时器资源,导致旧定时器仍在运行或引用已释放的对象
  2. async void陷阱:版本1使用async void,未捕获的异步异常会直接引发应用崩溃
  3. System.Threading.Timer被提前释放:版本3用using包裹Timer,方法执行完毕后Timer被销毁,回调无法触发
  4. 相机资源未重置:二次调用时相机实例可能已被释放或未重新初始化,导致拍照时出现空引用
  5. 线程安全问题:定时器回调在后台线程执行,直接操作MAUI相机(UI相关资源)会引发异常或空引用

各版本修复方案

版本1(Task.Delay)修复

替换async void为async Task,添加任务取消机制,确保相机资源非空:

private CancellationTokenSource _cts;

// 改用async Task避免未捕获异常崩溃
public async Task StartTimer(int quantity, int timerDelay)
{
    // 先终止并清理之前的任务
    if (_cts != null)
    {
        _cts.Cancel();
        _cts.Dispose();
    }
    _cts = new CancellationTokenSource();

    try
    {
        for (int i = 0; i < quantity; i++)
        {
            // 检查任务是否被取消,提前退出
            if (_cts.Token.IsCancellationRequested) break;

            if (i > 0)
            {
                await Task.Delay(timerDelay, _cts.Token);
            }

            // 拍照前必须确认相机实例非空
            if (CameraView.Camera == null)
            {
                throw new InvalidOperationException("相机未完成初始化");
            }
            // 执行拍照逻辑
            // var photo = await CameraView.CaptureAsync();
        }
    }
    catch (OperationCanceledException)
    {
        // 任务被取消,属于正常流程
    }
    catch (Exception ex)
    {
        Console.WriteLine($"拍照异常:{ex.Message}");
    }
    finally
    {
        _cts.Dispose();
        _cts = null;
    }
}

调用代码需改为await:

private async Task<bool> StartCapture()
{
    try 
    {
        // 先取消之前的捕获任务
        timer._cts?.Cancel();
        await timer.StartTimer(quantity, timerDelay);
        return true;
    }
    catch(Exception ex)
    {
        Console.WriteLine($"启动捕获失败:{ex.Message}");
        return false;
    }
}

版本2(System.Timers.Timer)修复

每次启动前清理旧定时器,添加线程同步,确保相机操作在主线程:

private System.Timers.Timer _aTimer;
private int _totalQuantity;
private int _totalTaken;
private readonly object _lockObj = new object();

public void StartTimer(int quantity, int timerDelay)
{
    // 强制停止并清理旧定时器
    StopTimer();

    // 线程安全初始化计数器
    lock(_lockObj)
    {
        _totalQuantity = quantity;
        _totalTaken = 0;
    }

    _aTimer = new System.Timers.Timer(timerDelay)
    {
        AutoReset = true,
        Enabled = false
    };
    _aTimer.Elapsed += SnapPic;
    _aTimer.Enabled = true;
}

private void StopTimer()
{
    if (_aTimer != null)
    {
        _aTimer.Elapsed -= SnapPic;
        _aTimer.Stop();
        _aTimer.Dispose();
        _aTimer = null;
    }
}

private void SnapPic(object sender, ElapsedEventArgs e)
{
    bool shouldStop = false;
    lock(_lockObj)
    {
        if (_totalTaken >= _totalQuantity)
        {
            shouldStop = true;
            return;
        }
        _totalTaken++;
    }

    if (shouldStop)
    {
        EndCapture(true);
        return;
    }

    // MAUI相机操作必须在主线程执行
    MainThread.BeginInvokeOnMainThread(async () =>
    {
        try
        {
            if (CameraView.Camera == null)
            {
                throw new InvalidOperationException("相机未完成初始化");
            }
            // 执行拍照逻辑
        }
        catch (Exception ex)
        {
            Console.WriteLine($"拍照异常:{ex.Message}");
            EndCapture(false);
        }
    });
}

private void EndCapture(bool success)
{
    StopTimer();
}

版本3(System.Threading.Timer)修复

移除using包裹,改为类级别的Timer字段,确保生命周期与类一致:

private System.Threading.Timer _bTimer;
private int _totalQuantity;
private int _totalTaken;
private readonly object _lockObj = new object();

public void StartTimer(int quantity, int timerDelay)
{
    // 清理旧定时器
    StopTimer();

    lock(_lockObj)
    {
        _totalQuantity = quantity;
        _totalTaken = 0;
    }

    // 不要用using,否则方法结束后Timer会被立即释放
    _bTimer = new System.Threading.Timer(SnapPic, null, 0, timerDelay);
}

private void StopTimer()
{
    if (_bTimer != null)
    {
        _bTimer.Dispose();
        _bTimer = null;
    }
}

private void SnapPic(object state)
{
    bool shouldStop = false;
    lock(_lockObj)
    {
        if (_totalTaken >= _totalQuantity)
        {
            shouldStop = true;
            return;
        }
        _totalTaken++;
    }

    if (shouldStop)
    {
        EndCapture(true);
        return;
    }

    // 切换到主线程执行相机操作
    MainThread.BeginInvokeOnMainThread(async () =>
    {
        try
        {
            if (CameraView.Camera == null)
            {
                throw new InvalidOperationException("相机未完成初始化");
            }
            // 执行拍照逻辑
        }
        catch (Exception ex)
        {
            Console.WriteLine($"拍照异常:{ex.Message}");
            EndCapture(false);
        }
    });
}

private void EndCapture(bool success)
{
    StopTimer();
}

关键注意事项

  • 相机资源校验:每次启动捕获前,重新确认相机权限和实例状态,必要时重新初始化CameraView
  • 定时器生命周期:绝不复用未清理的定时器实例,每次启动前必须停止并释放旧实例
  • 线程切换:所有MAUI UI/相机操作必须通过MainThread.BeginInvokeOnMainThread切换到主线程执行
  • 避免async void:异步方法优先使用async Task,便于异常捕获和任务管理
  • CancellationToken:用于快速终止未完成的定时任务,避免二次调用时出现资源冲突

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 03:28:09