问题
最近一个项目是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('全部完成了');
    },
  );
}