The general design problem can be described as:
I have a websocket connection that has a strict lifecycle to respect—it wants connect and disconnect to be called appropriately, and, because it talks to the system, it uses . Within this websocket connection, we have multiple different Subscription objects, each with a strict lifecycle that it wants to be respected (subscribe and unsubscribe), and it depends on the state of its parent websocket for those operations to be successful.
Here's a timeline of the ideal behavior for three nested lifecycle observables, where C depends on B which depends on A:
A = someInput.switchMap((i) => LifecycleObservable())
B = A.switchMap((a) => LifecycleObservable())
C = B.switchMap((b) => LifecycleObservable())
C.listen(print);
// <-- listen to c
// <-- produce [someInput]
setup A
setup B
setup C
// <-- c is produced
// <-- c is unsubscribed
teardown C
teardown B
teardown A
// <-- C is re-subscribed-to
setup A
setup B
setup C
// <-- produce [someInput]
teardown C
teardown B
teardown A
setup A
setup B
setup C
// <-- c is produced
First question: Is this an anti-pattern? I haven't been able to find much about this pattern on the web, but it seems like a pretty standard sort of thing you'd run into with observables: some objects just have a lifecycle and some objects might want to depend on that.
I can get pretty close to this ideal behavior using something like this:
class LifecycleObservable {
static Observable<T> fromObservable<T>({
#required Observable<T> input,
#required Future<void> Function(T) setup,
#required Future<void> Function(T) teardown,
}) {
return input.asyncMap((T _input) async {
await setup(_input);
return _input;
}).switchMap((T _input) {
return Observable<T>(Observable.never()) //
.startWith(_input)
.doOnCancel(() async {
await teardown(_input);
});
});
}
}
This code accepts a stream of stateful objects, running setup on them as they're produced and teardown on them as the sub-observable within the switchMap is cancelled.
The problem occurs when, in the original idealized timeline, the second [someInput] is produced: using the code above I get a callgraph like
// <-- listen to c
// <-- produce [someInput]
setup A
setup B
setup C
// <-- c is produced
// <-- produce [someInput]
teardown A
setup A
teardown B
setup B
teardown C
setup C
// <-- c is produced
the problem being that if B depends on A (like calling unsubscribe from a subscription that depends on an open websocket transport), this teardown order breaks the expected lifecycle of each object (the subscription tries to send unsubscribe over a closed websocket transport.
It seems to me that, quite simply, the observable pattern cannot express these semantics. Specifically, the observable pattern is not designed for cascading dependencies—parent observables know nothing about the state of their child observables.
I solved this problem for myself with the following dart code. I'm sure it's terrible, but it seems to work for me in general™.
class WithLifecycle<T> {
final FutureOr<void> Function() setup;
final FutureOr<void> Function() teardown;
final T value;
final WithLifecycle parent;
List<WithLifecycle> _children = [];
bool _disposed = false;
WithLifecycle({
#required this.value,
this.setup,
this.teardown,
this.parent,
});
void addDependency(WithLifecycle child) => _children.add(child);
void removeDependency(WithLifecycle child) => _children.remove(child);
Future<void> init() async {
parent?.addDependency(this);
await setup();
}
Future<void> dispose() async {
if (_disposed) {
return;
}
_disposed = true;
for (var _child in _children) {
await _child.dispose();
}
_children.clear();
await teardown();
}
}
which is then used to create the necessary dependency chain when using observables:
class LifecycleObservable {
static Observable<WithLifecycle<T>> fromObservable<T>({
#required Observable<T> value,
WithLifecycle parent,
#required Future<void> Function(T) setup,
#required Future<void> Function(T) teardown,
}) {
return value.concatMap((T _value) {
final withLifecycle = WithLifecycle<T>(
value: _value,
parent: parent,
setup: () => setup(_value),
teardown: () => teardown(_value),
);
return Observable<WithLifecycle<T>>(Observable.never())
.startWith(withLifecycle)
.doOnListen(() async {
await withLifecycle.init();
}).doOnCancel(() async {
await withLifecycle.dispose();
});
});
}
}
which is used like
token$ = PublishSubject();
channel$ = token$.switchMap((token) {
return LifecycleObservable.fromObservable<IOWebSocketChannel>(
value: Observable.just(IOWebSocketChannel.connect(Constants.connectionString)),
setup: (channel) async {
print("setup A ${channel.hashCode}");
},
teardown: (channel) async {
print("teardown A ${channel.hashCode}");
await channel.sink.close(status.goingAway);
});
});
streams$ = channel$.switchMap((channel) {
return LifecycleObservable.fromObservable<Stream<String>>(
parent: channel,
value: Observable.just(channel.value.stream.cast<String>()),
setup: (thing) async {
print("setup B ${thing.hashCode}");
},
teardown: (thing) async {
print("teardown B ${thing.hashCode}");
},
);
});
messages = streams$.flatMap((i) => i.value).share();
and ends up with a call graph like the below
// <- push [token]
flutter: setup A 253354366
flutter: setup B 422603720
// <- push [token]
flutter: teardown B 422603720
flutter: teardown A 253354366
flutter: setup A 260164938
flutter: setup B 161253018
Related
Let's assume that an initialization of MyComponent in Dart requires sending an HttpRequest to the server. Is it possible to construct an object synchronously and defer a 'real' initialization till the response come back?
In the example below, the _init() function is not called until "done" is printed. Is it possible to fix this?
import 'dart:async';
import 'dart:io';
class MyComponent{
MyComponent() {
_init();
}
Future _init() async {
print("init");
}
}
void main() {
var c = new MyComponent();
sleep(const Duration(seconds: 1));
print("done");
}
Output:
done
init
Probably the best way to handle this is with a factory function, which calls a private constructor.
In Dart, private methods start with an underscore, and "additional" constructors require a name in the form ClassName.constructorName, since Dart doesn't support function overloading. This means that private constructors require a name, which starts with an underscore (MyComponent._create in the below example).
import 'dart:async';
import 'dart:io';
class MyComponent{
/// Private constructor
MyComponent._create() {
print("_create() (private constructor)");
// Do most of your initialization here, that's what a constructor is for
//...
}
/// Public factory
static Future<MyComponent> create() async {
print("create() (public factory)");
// Call the private constructor
var component = MyComponent._create();
// Do initialization that requires async
//await component._complexAsyncInit();
// Return the fully initialized object
return component;
}
}
void main() async {
var c = await MyComponent.create();
print("done");
}
This way, it's impossible to accidentally create an improperly initialized object out of the class. The only available constructor is private, so the only way to create an object is with the factory, which performs proper initialization.
A constructor can only return an instance of the class it is a constructor of (MyComponent). Your requirement would require a constructor to return Future<MyComponent> which is not supported.
You either need to make an explicit initialization method that needs to be called by the user of your class like:
class MyComponent{
MyComponent();
Future init() async {
print("init");
}
}
void main() async {
var c = new MyComponent();
await c.init();
print("done");
}
or you start initialization in the consturctor and allow the user of the component to wait for initialization to be done.
class MyComponent{
Future _doneFuture;
MyComponent() {
_doneFuture = _init();
}
Future _init() async {
print("init");
}
Future get initializationDone => _doneFuture
}
void main() async {
var c = new MyComponent();
await c.initializationDone;
print("done");
}
When _doneFuture was already completed await c.initializationDone returns immediately otherwise it waits for the future to complete first.
I agree, an asynchronous factory function would help Dart devs with this problem. #kankaristo has IMHO given the best answer, a static async method that returns a fully constructed and initialized object. You have to deal with the async somehow, and breaking the init in two will lead to bugs.
I'm currently migrating an App's logic code from C# to Dart and I'm looking for a similiar collection type in Dart to C#s BlockingCollection. I basically want a queue where i can iterate infinitely. If the queue is empty it just waits until a new element is added.
Is that possible in Dart?
Best
You can use a StreamController.
Here I translated the first C# example for BlockingCollection
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
class BlockingCollectionDemo
{
static async Task Main()
{
await AddTakeDemo.BC_AddTakeCompleteAdding();
}
}
class AddTakeDemo
{
// Demonstrates:
// BlockingCollection<T>.Add()
// BlockingCollection<T>.Take()
// BlockingCollection<T>.CompleteAdding()
public static async Task BC_AddTakeCompleteAdding()
{
using (BlockingCollection<int> bc = new BlockingCollection<int>())
{
// Spin up a Task to populate the BlockingCollection
Task t1 = Task.Run(() =>
{
bc.Add(1);
bc.Add(2);
bc.Add(3);
bc.CompleteAdding();
});
// Spin up a Task to consume the BlockingCollection
Task t2 = Task.Run(() =>
{
try
{
// Consume consume the BlockingCollection
while (true) Console.WriteLine(bc.Take());
}
catch (InvalidOperationException)
{
// An InvalidOperationException means that Take() was called on a completed collection
Console.WriteLine("That's All!");
}
});
await Task.WhenAll(t1, t2);
}
}
}
to dart using a StreamController instead of BlockingCollection, and Future instead of Task.
import 'dart:async';
Future<void> main() async {
await addTakeCompleteAdding();
}
// Demonstrates:
// StreamController<T>.add()
// StreamController<T>.stream
// StreamController<T>.close()
Future<void> addTakeCompleteAdding() async {
StreamController<int> bc = StreamController<int>();
// Spin up a Future to populate the StreamController
Future<void> t1 = Future(() {
bc.add(1);
bc.add(2);
bc.add(3);
bc.close();
});
// Spin up a Future to consume the StreamController
Future<void> t2 = Future(() async {
// Consume consume the StreamController
await for (final element in bc.stream) {
print(element);
}
// Exits the loop when the stream is completed/closed
print("That's All!");
});
await Future.wait([t1, t2]);
}
That said, the StreamController differs a bit from BlockingCollection in that it is not a queue. A Stream in dart by default, can only have one subscription, unless you create a broadcast stream. Stream is more like an async enumerable in C#.
If you really need a queue data structure you can use the async package, which has a StreamQueue class that you can use to wrap the stream from the StreamController.
Here is the above code modified to use a StreamQueue:
import 'dart:async';
import 'package:async/async.dart';
Future<void> main() async {
await addTakeCompleteAdding();
}
// Demonstrates:
// StreamController<T>.add()
// StreamController<T>.stream
// StreamController<T>.close()
// StreamQueue<T>.next
Future<void> addTakeCompleteAdding() async {
StreamController<int> bc = StreamController<int>();
StreamQueue<int> queue = StreamQueue<int>(bc.stream);
// Spin up a Future to populate the StreamController
Future<void> t1 = Future(() {
bc.add(1);
bc.add(2);
bc.add(3);
bc.close();
});
// Spin up a Future to consume the StreamQueue
Future<void> t2 = Future(() async {
try {
while (true) {
// Consume consume the StreamQueue
print(await queue.next);
}
} on StateError catch (e) {
// A StateError means that next was called on a completed collection
print("That's all!");
}
});
await Future.wait([t1, t2]);
}
You can also write your own queue, based on futures instead of a stream:
import "dart:async" show Completer;
import "dart:collection" show Queue;
abstract class BlockingQueue<T> {
factory BlockingQueue() = _BlockingQueue;
Future<T> removeNext();
void add(T value);
}
class _BlockingQueue<T> implements BlockingQueue<T> {
final Queue<T> _writes = Queue();
final Queue<Completer<T>> _reads = Queue();
Future<T> removeNext() {
if (_writes.isNotEmpty) return Future.value(_writes.removeFirst());
var completer = Completer<T>();
_reads.add(completer);
return completer.future;
}
void add(T value) {
if (_reads.isNotEmpty) {
_reads.removeFirst().complete(value);
} else {
_writes.add(value);
}
}
}
You can also consider a double-blocking queue, where the add method also "blocks" if there is no-one to accept the value yet. It's not even that hard,.
import "dart:async" show Completer;
import "dart:collection" show Queue;
abstract class BlockingQueue<T> {
factory BlockingQueue() = _BlockingQueue;
Future<T> removeNext();
Future<void> add(T value);
}
class _BlockingQueue<T> implements BlockingQueue<T> {
final Queue<T> _writes = Queue();
final Queue<Completer<T>> _completers = Queue();
Future<T> removeNext() {
if (_writes.isNotEmpty) {
assert(_completers.isNotEmpty);
var completer = _completers.removeFirst();
completer.complete(_writes.removeFirst());
return completer.future;
}
var completer = Completer<T>();
_completers.add(completer);
return completer.future;
}
Future<void> add(T value) {
if (_writes.isEmpty && _completers.isNotEmpty) {
var completer = _completers.removeFirst();
completer.complete(value);
return completer.future;
}
var completer = Completer<T>();
_completers.add(completer);
_writes.add(value);
return completer.future;
}
}
That said, if you want to use a for (... in ...)-like loop, you probably do want to go with a Stream and use await for (... in theStream).
callbacks or asynchronous methods or other options
A solution to the callback plague is "await" and "async" or more specifacally 'dart:async' library.
Now, what is the cost of asynchrony?
When should we not use them?
What are the other alternatives?
The below is a badly coded non-polymer custom element that acts like a messageBox in desktop environment. It gives me less braces and parenthesis-es but requires the caller to be also async or use "show().then((v){print(v);});" pattern. Should I avoid the pattern like this?
Is callback better? Or there is an even smarter way?
Polling version
import 'dart:html';
import 'dart:async';
void init(){
document.registerElement('list-modal',ListModal);
}
class ListModal extends HtmlElement{
ListModal.created():super.created();
String _modal_returns="";
void set modal_returns(String v){
///use the modal_returns setter to
///implement a custom behaviour for
///the return value of the show method
///within the callback you can pass on calling append .
_modal_returns=v;
}
factory ListModal(){
var e = new Element.tag('list-modal');
e.style..backgroundColor="olive"
..position="absolute"
..margin="auto"
..top="50%"
..verticalAlign="middle";
var close_b = new DivElement();
close_b.text = "X";
close_b.style..right="0"
..top="0"
..margin="0"
..verticalAlign="none"
..backgroundColor="blue"
..position="absolute";
close_b.onClick.listen((_){
e.hide();
});
e.append(close_b,(_)=>e.hide());
e.hide();
return e;
}
#override
ListModal append(
HtmlElement e,
[Function clickHandler=null]
){
super.append(e);
if(clickHandler!=null) {
e.onClick.listen(clickHandler);
}else{
e.onClick.listen((_){
this.hide();
_modal_returns = e.text;
});
}
return this;
}
Future<String> show() async{
_modal_returns = '';
this.hidden=false;
await wait_for_input();
print(_modal_returns);
return _modal_returns;
}
wait_for_input() async{
while(_modal_returns=="" && !this.hidden){
await delay();
}
}
void hide(){
this.hidden=true;
}
Future delay() async{
return new Future.delayed(
new Duration(milliseconds: 100));
}
}
Non-polling version
In response to Günter Zöchbauer's wisdom(avoid polling), posting a version that uses a completer. Thanks you as always Günter Zöchbauer:
import 'dart:html';
import 'dart:async';
void init(){
document.registerElement('list-modal',ListModal);
}
class ListModal extends HtmlElement{
ListModal.created():super.created();
String _modal_returns="";
Completer _completer;
void set modal_returns(String v){
///use the modal_returns setter to
///implement a custom behaviour for
///the return value of the show method.
///Use this setter within the callback for
///append. Always call hide() after
///setting modal_returns.
_modal_returns=v;
}
factory ListModal(){
var e = new Element.tag('list-modal');
e.style..backgroundColor="olive"
..position="absolute"
..margin="auto"
..top="50%"
..verticalAlign="middle";
var close_b = new DivElement();
close_b.text = "X";
close_b.style..right="0"
..top="0"
..margin="0"
..verticalAlign="none"
..backgroundColor="blue"
..position="absolute";
close_b.onClick.listen((_){
e.hide();
});
e.append(close_b,(_){e.hide();});
e.hide();
return e;
}
#override
ListModal append(
HtmlElement e,
[Function clickHandler=null]
){
super.append(e);
if(clickHandler!=null) {
e.onClick.listen(clickHandler);
}else{
e.onClick.listen((_){
_modal_returns = e.text;
this.hide();
});
}
return this;
}
Future<String> show() async{
_modal_returns = '';
_completer = new Completer();
this.hidden=false;
return _completer.future;
}
void hide(){
hidden=true;
_completer?.complete(_modal_returns);
_completer=null;
}
}
Usually there is no question whether async should be used or not. Usually one would try to avoid it. As soon as you call an async API your code goes async without a possibility to choose if you want that or not.
There are situations where async execution is intentionally made async. For example to split up large computation in smaller chunks to not starve the event queue from being processed.
On the server side there are several API functions that allow to choose between sync and async versions. There was an extensive discussion about when to use which. I'll look it up and add the link.
The disadvantages of using async / await instead of .then() should be minimal.
minimal Dart SDK version with async / await support is 1.9.1
the VM needs to do some additional rewriting before the code is executed the first time, but this is usually neglectable.
Your code seems to do polling.
wait_for_input() async {
while(_modal_returns=="" && !this.hidden){
await delay();
}
}
This should be avoided if possible.
It would be better to let the modal manage its hidden state itself (by adding a hide() method for example), then it doesn't have to poll whether it was hidden from the outside.
Hello everyone
I have one class 'Example' who needs to do some computation. I call start() which call _next(). During the computation _next() calls itself couple of time but in my example I simulate that with a Timer. here is my code
import "dart:async";
main() {
Example ex = new Example();
for (var i = 0 ; i < 3 ; i++) {
ex.start().then((nbr) {
print(nbr);
});
}
}
class Example {
/// for _next
Completer _insideCompleter;
/// from start() to outside
Completer _outsideCompleter;
Example();
/// start is just a better public api than next when we start the exercise
Future<int> start() {
_insideCompleter = new Completer();
_outsideCompleter = new Completer();
_next().then((int value) {
print("value: $value");
_outsideCompleter.complete(value);
}).catchError((message) {
print("information: $message");
});
return _outsideCompleter.future;
}
/// _next handle the flow with the status
Future<int> _next() {
new Timer(new Duration(seconds: 6), () {
_insideCompleter.complete(15);
});
return _insideCompleter.future;
}
}
it finishes with : Bad state: Future already completed. but as you can see in the start(). the Completer are re-created with new. So I don't understand why it is already complete.
If anybody can explain why it's not correct to code like that and maybe give me some interesting links it would be great
Cheers!
I'm not entirely sure what your intention with the code is but I think you should either
Example ex = new Example();
for (var i = 0 ; i < 3 ; i++) { // create a new Example() for each iteration here
ex.start().then((nbr) { // or ensure that the next iteration is not executed
print(nbr); // before the previous is completed.
});
}
with this code ex.start() is called 3 times before the first call is completed.
Here the main issue is about completers called in the callback function
_next().then((int value) {
print("value: $value");
_outsideCompleter.complete(value); // this line
})
and
new Timer(new Duration(seconds: 6), () {
_insideCompleter.complete(15); // this line
});
Because this 2 function are called after your loop and your completer are attribute, all the callback will use the latest _outsideCompleter and _insideCompleter created.
So after than one of the callback have "consume" your completer, the others will create exception of 'Bad state: Future already completed'
here a version that works
import "dart:async";
main() {
Example ex = new Example();
for (var i = 0 ; i < 3 ; i++) {
ex.start().then((nbr) {
print(nbr);
});
}
}
class Example {
Example();
/// start is just a better public api than next when we start the exercise
Future<int> start() {
var outsideCompleter = new Completer(); // create localy each times
_next().then((int value) {
print("value: $value");
outsideCompleter.complete(value);
}).catchError((message) {
print("information: $message");
});
return outsideCompleter.future;
}
/// _next handle the flow with the status
Future<int> _next() {
var insideCompleter = new Completer(); // create localy each times
new Timer(new Duration(seconds: 6), () {
insideCompleter.complete(15);
});
return insideCompleter.future;
}
}
I basically know how to use them; for instance listening to the onClick Stream of an Element.
But, how do you set up your own Streams?
Simple example
Here's a complete working example:
import 'dart:async';
import 'dart:io';
class Application {
Stream onExit;
Application() {
// Create a stream controller and assign its stream to "onExit".
var controller = new StreamController();
onExit = controller.stream;
// Create some class that uses our stream.
new UserOfStream(this);
// Whenever we exit the application, notify everyone about it first.
controller.add('we are shutting down!');
exit(0);
}
}
class UserOfStream {
UserOfStream(app) {
app.onExit.listen((String message) => print(message));
}
}
main() => new Application();
You can also do cool things like check if there are subscribers with controller.hasListener or you can signal an error. Be sure to check the API documentation on StreamController.
You can use new StreamController.broadcast() for allowing multiple listeners.
For copy-pasters
Here's a simple way to create a stream (great snippet for copy-pasters):
class Something {
StreamController _onExitController = new StreamController.broadcast();
Stream get onExit => _onExitController.stream;
}
Then the class can just access _onExitController to control the stream (to for example .add()).
In addition to StreamController you can instantiate a Stream directly with one of its named constructors:
Stream.fromFuture() Returns a stream that fires one event (whatever the Future completes to.)
Stream.fromIterable() Returns a stream that converts the Iterable elements to a sequence of events.
Stream.periodic() Returns a stream that fires a computed event periodically.
This is very handy as you can write code that expects to consume a stream, but you have multiple choices as to how to feed events to that class. For example: Stream.fromIterable() could be used in a unit test to fire a known sequence of events to a class that otherwise normally would be fed data events read from a file.
I just created a new Dart library called event_stream to make creating custom events on your classes easier. Here is an example:
class ClassWithEvents implements NotifyPropertyChanged {
String _someProperty;
final EventStream<PropertyChangedEventArgs> _onPropertyChangedEvent = new EventStream<PropertyChangedEventArgs>();
Stream<PropertyChangedEventArgs> get onPropertyChanged => _onPropertyChangedEvent.stream;
final EventStream _onClosedEvent = new EventStream();
Stream get onClosed => _onClosedEvent.stream;
String get someProperty => _someProperty;
set someProperty(String value) {
_onPropertyChangedEvent.signal(new PropertyChangedEventArgs('someProperty', value));
_someProperty = value;
}
close() {
_onClosedEvent.signal();
}
}
main() {
var c = new ClassWithEvents();
c.onPropertyChanged.listen((PropertyChangedEventArgs<String> args) => print('changed: name=${args.propertyName} value=${args.value}'));
c.onClosed.listen((_) => print('closed'));
c.someProperty = "test";
c.close();
}
There is sample with from flutter bloc
Add dependency
rxdart: ^0.27.2
Create stream controller
final _todoStreamController = BehaviorSubject<List>.seeded(const []);
Update when there is a change
Future saveTodo(Todo todo) {
final todos = [..._todoStreamController.value];
final todoIndex = todos.indexWhere((t) => t.id == todo.id);
if (todoIndex >= 0) {
todos[todoIndex] = todo;
} else {
todos.add(todo);
}
_todoStreamController.add(todos);
}
Broadcast
Stream<List> getTodos() => _todoStreamController.asBroadcastStream();
Subscribe
Future _onSubscriptionRequested(
TodosOverviewSubscriptionRequested event,
Emitter emit,
) async {
emit(state.copyWith(status: () => TodosOverviewStatus.loading));
await emit.forEach<List<Todo>>(
_todosRepository.getTodos(),
onData: (todos) => state.copyWith(
status: () => TodosOverviewStatus.success,
todos: () => todos,
),
onError: (_, __) => state.copyWith(
status: () => TodosOverviewStatus.failure,
),
);
}
REF LINK