在Flutter中使用信号量解决异步冲突

问题

最近一个项目是IM的项目,使用的是悟空IM SDK,在会话列表中,会话列表的数组conversationList会被多次改变:

  • 读取本地数据库设置会话列表
  • 根据服务器接口返回更新会话列表
  • 频道刷新回调(包括频道新增、频道信息修改、删除频道),在会话列表中主要处理频道信息修改时要将对应的频道名与频道头像更新,在频道被删除时应将频道对应的会话删除
  • 会话列表刷新回调(会话新增、更新),在会话列表刷新中,处理会话的新增、更新,已读未读
  • 会话删除回调,处理会话的删除
  • 频道消息列表回调,主要处理更新会话的最新一条消息,设置为未读
  • cmd消息,这里主要是处理服务器的已读回复,将对应会话设置为已读,更新已读扩展(WKMsgExtra extra
  • 置顶会话,这里主要处理会话列表排序

在以上的多次改变中,由于它们的改变都是不定时的,也许会出现不同的几个操作同时修改conversationList,这样会造成数据源的更新冲突,在界面上产生未知错误。

思路

如果在iOS或者安卓中,一般可以采取锁或者队列的方式来解决,但是在Flutter中,由于Dart单线程,异步采取Future事件队列和微队列的方式,它们之间其实并没有一个顺序控制。

在iOS中,有一个信号量可以控制线程任务的执行顺序(也可以用于控制最大并发、锁),所以我基于信号量的定义,在Flutter中也实现了一个信号量的控制方法,用于解决这些问题,代码如下:

dart 复制代码
class SemaphoreTask {
  final int _maxCount;
  int _currentCount = 0;
  final _waiting = <Completer<void>>[];

  SemaphoreTask(this._maxCount);

  Future<void> acquire() async {
    if (_currentCount < _maxCount) {
      _currentCount++;
      return;
    }
    final completer = Completer<void>();
    _waiting.add(completer);
    await completer.future;
  }

  void release() {
    if (_waiting.isNotEmpty) {
      _waiting.removeAt(0).complete();
    } else {
      _currentCount--;
    }
  }

  static Future<void> runTasksWithSemaphore(
    List<Future Function()> tasks, {
    int maxConcurrent = 3,
    void Function()? callback, // 全部完成的回调
  }) async {
    final semaphore = SemaphoreTask(maxConcurrent);
    final futures = <Future>[];

    for (final task in tasks) {
      futures.add(() async {
        await semaphore.acquire();
        try {
          await task();
        } finally {
          semaphore.release();
        }
      }());
    }

    await Future.wait(futures);
    if (callback != null) {
      callback();
    }
  }

  dispose() {
    for (var completer in _waiting) {
      completer.complete();
    }
    _waiting.clear();
  }
}

如上所示,我们创建了一个基于Dart的信号量,其中_maxCount表示最大并发数,_currentCount表示信号量的初始值(最大并发数),使用Completer来阻塞Future

  • acquire()方法用于信号量+1
  • release()方法用于信号-1
  • runTasksWithSemaphore是一个便捷方法,用于传入多个异步任务,进行最大并发数控制
  • dispose()用于页面退出释放资源

解决

这下,我们可以顺利使用信号量了,我们的需求是需要制造一个异步队列,所以,首先,我们定义一个宽度为1的信号量:

dart 复制代码
final semaphore = SemaphoreTask(1);

然后,在每一个需要改变数据源conversationList的地方,进行顺序控制:

dart 复制代码
Future(() async {
  await semaphore.acquire();

  ///根据是否置顶和置顶时间排序
  conversationList = WkConversationUtil.instance.sortListByConversation(conversationList);

  update();// 这里是使用GetX进行状态管理
  semaphore.release();
});

最后,在释放的地方,GetX中是在onClose()方法中:

dart 复制代码
@override
void onClose() {
  super.onClose();
  semaphore.dispose();
}

结果

经过多轮测试,会话列表正常展示,未再有出现多个相同会话、会话列表信息展示错误这些问题,并且,会话列表消息流畅,并未有卡顿或者遗漏,问题顺利得到解决。

并发控制

附上写信号量时的测试代码:

dart 复制代码
void main(List<String> args) {
  print('开始了');
  final tasks = List.generate(
      10,
      (i) => () async {
            print('Task $i started');
            await Future.delayed(Duration(seconds: 1));
            print('Task $i completed');
            return i;
          });

  SemaphoreTask.runTasksWithSemaphore(
    tasks,
    maxConcurrent: 3,
    callback: () {
      print('全部完成了');
    },
  );
}
相关推荐
天空之城--37 分钟前
Android Flutter行业动态与学习参考(2026年8月)
android·flutter
FungLeo2 小时前
Flutter Web 中文字体困境与渲染器选择:CanvasKit vs HTML renderer 实战
前端·flutter·html
杉氧2 小时前
原生交互:如何在 Flutter 中嵌入 Android/iOS 原生组件并解决手势冲突
android·flutter·dart
恋猫de小郭4 小时前
Flutter iOS Deep Link 为什么会突然失效:一系列难以言喻的问题
android·前端·flutter
FungLeo17 小时前
Flutter fl_chart 0.70 破坏性 API 迁移实录:duration/getTooltipColor/withValues 全解
flutter·fl_chart
FungLeo1 天前
Flutter 吸顶分组列表实战:语义桶分组 + 点击头平滑滚动
android·flutter
FungLeo1 天前
Flutter 超长 StatefulWidget 拆分术:part of + extension on State 实战
android·flutter·dart
GitLqr1 天前
Impeller 时代:Shader Jank 消失了,但渲染性能的战场也变了
flutter·面试·性能优化
恋猫de小郭1 天前
Flutter 的另外一种形态?社区 DartNative 要来了。
android·前端·flutter
阿里云云原生2 天前
还原一次用户等待:深度解析 Flutter RUM SDK 如何打通 Dart 到 Native 的观测链路
flutter