dartcontrollerstreamsynchronizationfuture

Dart: Explain SynchronousStreamController with an example


I have gone through the doc of SynchronousStreamController manytimes.

I know that it delivers its events synchronously; events are fired immediately to the stream instead of adding to a later micro-task, causing extra latency.

But I still having hard-time comprehending the concept when I tried to illustrate the explanation of doc with a few examples of my own, however I failed. I get the same results for StreamController({bool sync = false}) and StreamController({bool sync = true}).

Unfortunately, the doc does not present any examples other than explanation. Can someone help me by giving a simple working example that can show the difference when sync parameter is set false and true?


Solution

  • The synchronous stream controller delivers events when you call add, the asynchronous controller schedules a later event to deliver the event. (Caveat: unless the stream subscription is paused, then they both delay delivery until it gets resumed.)

    The difference can be seen from code like:

    import "dart:async";
    void main() async {
      var syncC = StreamController<int>(sync: true);
      var asyncC = StreamController<int>(); 
      syncC.stream.listen((e) { 
        print("Sync : $e");
      });
      asyncC.stream.listen((e) { 
        print("Async: $e");
      });
      print("Step 1");
      asyncC.add(37);
      print("Step 2");
      syncC.add(42); // Prints "Sync : 42" *right here*.
      print("Step 3");
      await Future.delayed(Duration.zero); // Prints "Async: 37" while waiting here.
      print("Step 4");
    }
    

    This code prints

    Step 1
    Step 2
    Sync : 42
    Step 3
    Async: 37
    Step 4
    

    That shows that the synchronous controller event is received immediately when you call add. The asynchronous controller event is received in a later event (microtask to be precise.)

    So, what does that mean, and when should you use a synchronous controller?

    The answer to the latter is "Almost never, going on absolutely never."

    Synchronous controllers exist as a primitive to allow people to write other streams manipulation libraries without introducing extra delays. You should rarely be using a stream controller at all, preferring to use an async* method to create the stream.

    If you are receiving events from something which is not a future or stream, you may need to convert that to a stream, and then a stream controller can be the thing you need. In that case, if you are absolutely certain that you receive your incoming triggers as asynchronous events, it's OK to use a synchronous controller to emit an event at the same time.

    It's not OK to call add on a synchronous controller in response to something getting called directly by user code. That includes in response to the stream being listened to (the onListen callback) or resumed (onResume).

    A synchronous broadcast controller is extra dangerous to use, because it will throw if you try to add an event (calling add) while another event is being delivered. That can't happen for a single-subscription stream, because delivery happens atomically, and it won't matter for an async controller, because it'll just keep the event and deliver it later.

    Use synchronous controllers with care. If in doubt, don't use them at all.