Dart异步缓存代码触发TimeoutException: Future not completed问题咨询
代码核心问题分析
- 第一个严重问题:异常场景下未清理
pendingFutures的key
你当前的pendingFutures.remove逻辑只写在查询成功的执行路径下,一旦connection.query抛出TimeoutException或者其他异常,代码会直接跳转至catch块,永远不会执行remove操作。
Dart的Future会永久缓存执行结果(包括异常),后续同一个sql的请求会命中if(pendingFutures.containsKey)分支,await这个已经抛出过超时异常的Future,会直接重复抛出之前的TimeoutException,这就是你偶发报错的核心诱因。而且此时await直接抛出异常,你后面的print("This Line is already do not prints")自然不会执行,完全符合你遇到的现象。 - 第二个问题:存在并发竞争的隐患
当多个请求同时查询同一个sql时,可能出现:请求A刚执行完remove操作删除key,请求B刚完成containsKey判断为true,接下来await pendingFutures[sqlQuery.hashCode]时取到的是null,虽然Dart里await null不会报错,但不符合你的业务设计预期。 - 第三个问题:不必要的await操作
pendingFutures.remove是Map的同步方法,返回的是被删除的元素而非Future,前面加await属于多余写法,虽然不会直接导致报错,但属于不规范代码。
Future默认超时机制说明
Dart标准库的原生Future没有默认超时机制,你遇到的超时是connection.query方法自身实现的超时逻辑,和你传入的timeoutInSeconds: 3600参数对应,超时后主动抛出TimeoutException,不是Dart Future的默认行为。
修复代码示例
Future<void> addResultToCache(String sqlQuery) async { final queryKey = sqlQuery.hashCode; try { if(pendingFutures.containsKey(queryKey)) { print('This query is already running'); final runningFuture = pendingFutures[queryKey]; // 额外判空避免并发删除导致的空值问题 if (runningFuture != null) { await runningFuture; } print("This Line will now print normally"); } else { final queryFuture = connection.query(sqlQuery, timeoutInSeconds: 3600); pendingFutures[queryKey] = queryFuture; try { var queryResult = await queryFuture; var queryObj = RequestCacheStruct(queryKey, DateTime.now(), queryResult!); requestsCacheList.add(queryObj); print('Query was added to cache'); } finally { // 不管查询成功还是失败,最终都清理key pendingFutures.remove(queryKey); } } } on TimeoutException catch(e) { print('TimeoutException: ${e.message}' ); } }
内容的提问来源于stack exchange,提问作者Dmitry Bubnenkov
相关产品推荐
相关产品推荐

