.NET MAUI中Timer二次调用时Task.Delay空引用异常解决问询
.NET 7 MAUI安卓定时拍照二次调用异常解决方案
核心问题分析
- 定时器状态污染:复用TimerTrigger实例时,未清理之前的定时器资源,导致旧定时器仍在运行或引用已释放的对象
- async void陷阱:版本1使用
async void,未捕获的异步异常会直接引发应用崩溃 - System.Threading.Timer被提前释放:版本3用
using包裹Timer,方法执行完毕后Timer被销毁,回调无法触发 - 相机资源未重置:二次调用时相机实例可能已被释放或未重新初始化,导致拍照时出现空引用
- 线程安全问题:定时器回调在后台线程执行,直接操作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
相关产品推荐
相关产品推荐

