技术问题甜点

开发过程中遇到的竞态条件问题

#问题现象

在开发Flutter视频播放器时,遇到了一个非常有意思的现象:

  • 当前播放第3个片段,向后跳到6等不正常,会先跳到片段6,然后再跳到4 这个现象非常奇怪?

#问题代码

// 用户手动切换片段
void _switchToSegment(int index) {
    // 切换视频,设置新的播放位置
    await _processSegmentBoundaries(newSegment);
    _controller!.play();
}

// 视频播放监听器
void _videoListener() {
    // 检测是否播放到片段结尾
    if (currentPosition >= adjustedEndTime) {
        _handleNextSegment(); // 这里又会调用 _switchToSegment
    }
}

#调试发现的问题

当调用 _switchToSegment(index) 时:

  1. 向后跳(如3→6)
    • 调用 _switchToSegment(6)
    • 视频跳转到第6个片段的开始位置
    • _videoListener 检测当前播放位置,发现已经大于原片段的 adjustedEndTime
    • 那么则调用_handleNextSegment方法, 跳转到3的下一个片段4

核心问题_videoListener_switchToSegment 还没完全初始化完成时就开始工作了。

#解决思路

问题的本质是竞态条件:用户手动切换和自动切换逻辑产生了冲突。 解决思路很简单:加一个标志位,让这两个操作互相隔离

#解决方案

class VideoPlayerState extends State<VideoPlayer> {
  // 添加标志位
  bool _isSwitchingSegment = false;
  
  void _videoListener() {
    // 如果正在手动切换片段,忽略自动切换逻辑
    if (_isSwitchingSegment) {
      return;
    }
    
    // 原有的自动切换逻辑
    if (currentPosition >= adjustedEndTime) {
      _handleNextSegment();
    }
  }
  
  Future<void> _switchToSegment(int index) async {
    // 设置标志位
    _isSwitchingSegment = true;
    
    try {
      // 执行切换逻辑
      await _processSegmentBoundaries(newSegment);
      setState(() {
        _currentSegmentIndex = index;
        _isPlaying = true;
      });
      _controller!.play();
    } finally {
      // 无论成功失败,都要重置标志位
      _isSwitchingSegment = false;
    }
  }
}

#总结

这个bug很好地展示了异步编程中的经典问题:竞态条件

  • 现象:看似简单的功能,却出现了方向性的差异
  • 本质:两个异步操作之间的时序冲突
  • 解决:通过状态标志位进行操作隔离 这提醒我们在处理用户交互和自动逻辑并存的场景时,一定要考虑它们之间的相互影响,必要时需要加入状态管理来避免冲突。

有问题请留言~

在 GitHub 查看讨论