Dart: Do I have to cancel Stream subscriptions and close StreamSinks?
Asked Answered
M

2

52

I know I have to cancel Stream Subscriptions when I no longer want to receive any events. Do I have to this even after I receive a 'Done' event? Or do I get memory leaks?

What happens to Streams that are passed to addStream of another Stream? Are they automatically canceled?

Same Question on the StreamSink side do I have to close them if the stream is already done?

Manhandle answered 17/4, 2018 at 13:28 Comment(1)
I had a case where a screen was listening to a stream and based on the response the screen was being popped. A weird thing I noticed was that the listener was still active even though the screen didn't exist anymore. What I did was stored the StreamSubscription in a variable and closed it in the dispose method of the screen.Pleurisy
G
61

Short-answer: no, but you should. Nothing in the contract of either StreamSubscription or StreamSink requires closing the resources, but some use cases can lead to memory leaks if you don't close them, even though in some cases, doing so might be confusing. Part of the confusion around these classes is that they are overloaded, and handle two fairly distinct use cases:

  1. Resource streams (like file I/O, network access)
  2. Event streams (like click handlers)

Let's tackle these subjects one at a time, first, StreamSubscription:

StreamSubscription

When you listen to a Stream, you receive a StreamSubscription. In general, when you are done listening to that Stream, for any reason, you should close the subscription. Not all streams will leak memory if choose not to, but, some will - for example, if you are reading input from a file, not closing the stream means the handle to the file may remain open.

So, while not strictly required, I'd always cancel when done accessing the stream.

StreamSink

The most common implementation of StreamSink is StreamController, which is a programmatic interface to creating a Stream. In general, when your stream is complete (i.e. all data emitted), you should close the controller.

Here is where it gets a little confusing. Let's look at those two cases:

File I/O

Imagine you were creating an API to asynchronously read a File line-by-line:

Stream<String> readLines(String path);

To implement this, you might use a StreamController:

Stream<String> readLines(String path) {
  SomeFileResource someResource;
  StreamController<String> controller;
  controller = new StreamController<String>(
    onListen: () {
      someResource = new SomeFileResource(path);
      // TODO: Implement adding to the controller.
    },
  );
  return controller.stream;
}

In this case, it would make lots of sense to close the controller when the last line has been read. This gives a signal to the user (a done event) that the file has been read, and is meaningful (you can close the File resource at that time, for example).

Events

Imagine you were creating an API to listen to news articles on HackerNews:

Stream<String> readHackerNews();

Here it makes less sense to close the underlying sink/controller. Does HackerNews ever stop? Event streams like this (or click handlers in UI programs) don't traditionally "stop" without the user accessing for it (i.e cancelling the StreamSubscription).

You could close the controller when you are done, but it's not required.


Hope that makes sense and helps you out!

Gibber answered 17/4, 2018 at 17:24 Comment(3)
Thanks! Only your short answer isn't consistent with your later elaboration. But it gives me a better understanding now. Thanks a lotManhandle
I'd argue that the answer really is "yes, you should", and the "no, you don't have to" is only technically correct (as elaborated by the remaining text). That is, in some cases you don't need to, but it's easier to always clean up after yourself (and leave the exceptions to the few cases where it's not easier and you know that you don't need to). It's easier to understand code when you can see that a listen is matched by a cancel on the subscription, than looking at the code and not seeing any signs of the object life cycle.Theta
I thought Dart didn't allow overloading. What did you mean by that?Gwen
B
17

I found in my case that if I have code like this:

Stream<String> readHackerNews(String path) {  
  StreamController<String> controller = StreamController<String>();

  ......

  return controller.stream;
}

I see a warning message "Close instance of 'dart.core.Sink'." in the Visual Studio Code. In order to fix this warning I added

controller.close()

to the event handler for the OnCancel event, see below:

Stream<String> readHackerNews(String path) {  
  StreamController<String> controller = StreamController<String>();

  //TODO: your code here

  controller.onCancel = () {
    controller.close();
  };

  return controller.stream;
}

Hope this helps!

Brinson answered 15/11, 2019 at 6:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.