In Dart, why waiting for a trivial ("empty") StreamController stream immediately exits program?

Viewed 318

This is the backbone of a simple program that uses a StreamController to listen on some input stream and outputs some other data on its own stream as a reaction:

import 'dart:async';     

main() async {     
    var c = StreamController(         
            onListen: (){},         
            onPause: (){},         
            onResume: (){},         
            onCancel: (){});         

    print("start");         
    await for (var data in c.stream) {         
            print("loop");         
    }         

    print("after loop");         
}     

Output:

$ dart cont.dart
start
$ dart

Why does this code exit immediately at the await for line without executing neither print("loop") nor print("after loop") ?

Note: in the original program, onListen() will receive the input stream and subscribe to it. The loop actually works until calling subscription.cancel() on the input stream, when it also suddenly exits, without any chance to clean up.

1 Answers

I was surprised by this, but then again all of async/await code is converted to .then()s and it seems everything can't be perfectly translated.

It seems this is part of the answer: You need to close the stream in another task to get out of the await for. Here is a previous related question. Since we don't close it, lines after await for do not execute. Here's an example that avoids this:

import 'dart:async';

main() async {
  var c = StreamController(
      onListen: () {},
      onPause: () {},
      onResume: () {},
      onCancel: () {});

  print("start");
  await Future.wait(<Future>[producer(c), consumer(c)]);

  print("after loop");
}

Future producer(StreamController c) async {
  // this makes it print "loop" as well
  // c.add("val!"); 
  await c.close();
}

Future consumer(StreamController c) async {
  await for (var data in c.stream) {
    print("loop");
  }
}

which prints:

start
after loop

So, moral of the story is,

  1. Never leave a StreamController unattended or weird things will happen.
  2. Some async functions that you wait on will break your code, even bypassing your try/finally block!

I thought I knew async/await inside out, but this second point scares me now.

Related