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

Flutter中WebSocket断开后TimeoutException致页面回退问题求助

问题分析

核心矛盾在于:WebSocket断开时触发页面跳转,但未完成的异步命令任务仍在后台执行,后续的超时异常处理或UI操作(如隐藏loading)会导致已跳转的命令页重新回到前台。本质是异步任务与页面生命周期未同步,且未主动终止pending的请求。

解决方案

通过「主动终止pending请求」+「组件生命周期校验」双维度解决问题:

1. 改造WebSocket管理类,跟踪并终止待处理请求

在WebSocket类中维护所有待执行的命令请求,断开时主动取消订阅并结束异步任务,避免后续超时逻辑触发:

class WebSocketManager {
  // 其他原有成员
  final List<_PendingRequest> _pendingRequests = [];

  // 内部类存储待处理请求信息
  class _PendingRequest {
    final Completer<dynamic> completer;
    final StreamSubscription<dynamic>? subscription;
    final Command command;

    _PendingRequest({
      required this.completer,
      required this.subscription,
      required this.command,
    });
  }

  // 改造sendCommandAndWaitForResponse方法
  Future<dynamic> sendCommandAndWaitForResponse(Command command, {Duration timeout = const Duration(seconds: 30)}) async {
    final completer = Completer<dynamic>();
    StreamSubscription<dynamic>? subscription;

    subscription = _messageController.stream.listen((message) {
      if (message == 'disconnected') {
        _removePendingRequest(command.number);
        completer.completeError('WebSocket disconnected');
      } else {
        final decodedMessage = jsonDecode(message);
        if (decodedMessage['Number'] == command.number) {
          _removePendingRequest(command.number);
          completer.complete(decodedMessage);
        }
      }
    });

    // 将请求加入待处理列表
    _pendingRequests.add(_PendingRequest(
      completer: completer,
      subscription: subscription,
      command: command,
    ));

    sendCommand(command);

    return completer.future.timeout(timeout, onTimeout: () {
      _removePendingRequest(command.number);
      subscription?.cancel();
      return Future.error('WebSocket response timeout ${command.commandType}');
    });
  }

  // 移除指定编号的待处理请求
  void _removePendingRequest(int commandNumber) {
    _pendingRequests.removeWhere((req) => req.command.number == commandNumber);
  }

  // WebSocket断开时,终止所有待处理请求
  void onDisconnected() {
    // 遍历取消所有订阅并结束异步任务
    for (var req in _pendingRequests) {
      req.subscription?.cancel();
      if (!req.completer.isCompleted) {
        req.completer.completeError('WebSocket disconnected');
      }
    }
    _pendingRequests.clear();
    // 发送断开通知到消息流
    _messageController.add('disconnected');
  }
}

2. 页面跳转时先终止pending请求,异步任务中校验组件状态

在命令页面的断开回调中先通知WebSocket终止请求,同时在异步任务中多次校验组件是否仍挂载,避免卸载后执行UI操作:

// WebSocket断开回调
void onWebSocketDisconnected() {
  logger.log("WebSocket disconnected");
  // 先终止所有待处理请求
  widget.websocket.onDisconnected();
  
  Navigator.pushReplacement(
    context,
    MaterialPageRoute(
      builder: (context) => AuthenticationPage(
        websocket: widget.websocket,
        logger: logger,
      ),
    ),
  );
}

// 改造命令发送方法
Future<void> _showUtility() async {
  logger.log("Sending 'Show Utility' command...");

  if (!mounted) return; // 组件已卸载则直接返回
  loadingOverlay.show();

  var command = ShowUtilityCommand(
    number: widget.websocket.messageCounter,
  );

  try {
    Map<String, dynamic> responseJson = await widget.websocket.sendCommandAndWaitForResponse(command);
    if (!mounted) return; // 响应返回时校验组件状态
    Response response = Response.fromJson(responseJson);
    // 后续业务逻辑同样需先校验mounted
  } on TimeoutException catch (e) {
    logger.log('Error: Timeout - ${e.message}');
    if (!mounted) return;
  } on String catch (e) {
    if (e == 'WebSocket disconnected') {
      logger.log('Error: WebSocket disconnected');
    } else {
      logger.log('Error: $e');
    }
    if (!mounted) return;
  } catch (e) {
    logger.log('Error: $e');
    if (!mounted) return;
  }

  if (!mounted) return;
  loadingOverlay.hide();
}
关键说明
  • 主动终止pending请求:WebSocket断开时遍历所有待处理请求,直接结束异步任务,避免超时逻辑触发。
  • 组件状态校验:利用Flutter State类的mounted属性,在异步任务的关键节点(显示loading、处理响应、隐藏loading)校验组件是否仍存活,避免卸载后执行UI操作导致页面异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 15:35:28