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

Flutter Bloc异步函数调用emit触发断言错误问题排查

问题:Flutter Bloc中Timer周期性任务调用emit触发断言错误

你在Bloc的定时任务中调用emit时触发了_AssertionError,错误定位在periodicFetch方法的emit(WalletListState(wallets: wallets!))语句。

相关代码

Future<void> fetchInitialData(FetchInitialDataEvent event, Emitter<WalletState> emit) async {
    if (wallets != null) {
      emit(WalletListState(wallets: wallets!));
    } else {
      walletsModel = await getWallets();
      assetsModel = await getSupportedAssets();
      cmc = await getCmc();
      currencies = await getCurrencies();

      fetchAddress();

      wallets = walletEngine.buildWallet(assetsModel, walletsModel, cmc);
      if (wallets != null && configEngine.filteredMarkets == null && configEngine.orgMarkets != null) {
        configEngine.filteredMarkets = walletEngine.filterMarket(wallets!, configEngine.orgMarkets!);
        openSocket.init(configEngine.filteredMarkets!);
      }
      emit(WalletListState(wallets: wallets!));
    }

    if (currencies != null) {
      listenTickerStream(currencies!, coins);
    }
    startWalletScheduler(emit);
  }

startWalletScheduler(Emitter<WalletState> emit) {
    if (walletScheduler?.isActive != true) {
      Duration interval = Duration(seconds: walletIntervalSeconds);
      walletScheduler = Timer.periodic(interval, (timer) async {
        await periodicFetch(emit);
      });
    }
  }

  Future<void> periodicFetch(Emitter<WalletState> emit) async {
    walletsModel = await getWallets();
    wallets = walletEngine.buildWallet(assetsModel, walletsModel, cmc);
    emit(WalletListState(wallets: wallets!));
  }

Future<WalletsResponse?> getWallets() async {
    final result = await repo.wallets(cancelToken: cancelToken);

    return result.fold((error) {
      return Future.value(null);
    }, (resp) {
      return resp;
    });
  }

错误栈

_AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:51:61)
I/flutter ( 8440): #1 _AssertionError._throwNew (dart:core-patch/errors_patch.dart:40:5)
I/flutter ( 8440): #2 _Emitter.call (package:bloc/src/emitter.dart:114:7)
I/flutter ( 8440): #3 WalletBloc.periodicFetch (package:drip_mobile/src/core/wallet/biz/wallet_bloc.dart:122:9)
I/flutter ( 8440): #4 WalletBloc.startWalletScheduler.<anonymous closure> (package:drip_mobile/src/core/wallet/biz/wallet_bloc.dart:114:9)

问题原因

Bloc的Emitter实例是绑定到当前事件处理周期的,当fetchInitialData事件处理完成后,这个Emitter会被标记为无效。你把该Emitter传递给定时任务并在回调中使用,此时原事件周期已结束,调用emit就会触发断言错误。

解决方法

不要跨周期复用Emitter,而是通过Bloc的add方法发送新事件,让Bloc的标准事件处理流程来更新状态:

修改步骤

  1. 定义新事件用于触发钱包刷新:
class RefreshWalletsEvent extends WalletEvent {}
  1. 在Bloc中添加该事件的处理逻辑(以Bloc v8+的on语法为例):
on<RefreshWalletsEvent>((event, emit) async {
  walletsModel = await getWallets();
  wallets = walletEngine.buildWallet(assetsModel, walletsModel, cmc);
  emit(WalletListState(wallets: wallets!));
});
  1. 修改定时任务逻辑,改为发送事件而非直接调用emit:
void startWalletScheduler() {
  if (walletScheduler?.isActive != true) {
    Duration interval = Duration(seconds: walletIntervalSeconds);
    walletScheduler = Timer.periodic(interval, (timer) {
      add(RefreshWalletsEvent());
    });
  }
}
  1. 调整fetchInitialData中的调用,不再传递emit:
// 替换原startWalletScheduler(emit);
startWalletScheduler();

异步函数中高效使用emit的建议

  • 禁止跨事件周期复用Emitter:Emitter仅在当前事件处理的生命周期内有效,事件处理完成后必须丢弃。
  • 通过事件触发状态更新:定时任务、异步回调等脱离当前事件周期的操作,一律通过add方法发送新事件,让Bloc标准流程处理状态变更。
  • 状态更新逻辑封装到事件处理中:所有emit操作应该放在事件处理回调内,保证状态流的可追踪性和一致性。
  • 旧版Bloc使用async*流:如果使用Bloc v7及以下版本,在mapEventToState中用async*配合yield发送状态,天然适配异步场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 18:02:38