Dart中取消stdin订阅失效导致程序无法正常退出的问题
Dart异步读取标准输入后无需调用
io.exit即可退出的解决方案 我在Dart 3.7.0中尝试异步读取标准输入行,测试了几种方案后发现一个共同问题:程序逻辑执行完毕后,必须调用io.exit(0)才能让进程退出,否则会一直挂起。即使按照文档调用await subscription.cancel()取消流订阅,也无法解决这个问题,已在Debian 12和Windows 10上验证。
原示例代码
import 'dart:async' as async; import 'dart:io' as io; import 'dart:convert'; class ReadLineAsync { final stream = io.stdin .asBroadcastStream() .transform(utf8.decoder) .transform(const LineSplitter()); Future<String> readLineAsync() async { final completer = async.Completer<String>(); late async.StreamSubscription<String> subscription; subscription = stream.listen((line) async { await subscription.cancel(); if (!completer.isCompleted) completer.complete(line); }, cancelOnError: true); return completer.future; } } Future<void> menu() async { final reader = ReadLineAsync(); for (;;) { var line = await reader.readLineAsync(); switch( line.trim() ) { case 'q': return; default: print('Try again!'); } } } void main() async { await menu(); print('Bye'); // io.exit(0); // 注释后程序无法退出 }
问题原因
问题出在io.stdin的底层流特性上——只要存在活跃的订阅(包括广播流关联的原始流订阅),Dart的事件循环就会保持运行状态。即使取消了广播流的单个订阅,广播流仍可能持有原始stdin流的订阅,导致进程无法自动退出。
解决方案
方案1:主动关闭stdin流
在程序逻辑结束后,主动关闭io.stdin流,彻底终止所有与标准输入相关的资源占用,让事件循环正常结束。修改后的main函数如下:
void main() async { await menu(); print('Bye'); await io.stdin.close(); // 主动关闭stdin流,无需调用io.exit }
方案2:优化读取逻辑,避免广播流
取消asBroadcastStream的使用,每次读取直接监听原始stdin流,读取完成后立即取消订阅,从根源减少残留订阅的可能:
class ReadLineAsync { Future<String> readLineAsync() async { final completer = async.Completer<String>(); late async.StreamSubscription<String> subscription; subscription = io.stdin .transform(utf8.decoder) .transform(const LineSplitter()) .listen((line) async { await subscription.cancel(); if (!completer.isCompleted) completer.complete(line); }, cancelOnError: true); return completer.future; } }
这种方式每次读取都会创建独立的流转换链,读取完成后直接取消原始stdin流的订阅,配合程序结束时关闭stdin,能确保进程正常退出。
内容的提问来源于stack exchange,提问作者user2574
相关产品推荐
相关产品推荐

