I have this program that I'm using to learn about Dart's async programming.
import 'dart:io';
Future<int> sumStream(Stream<int> stream) async {
var sum = 0;
await for (final value in stream) {
print('consuming event $value');
sum += value;
}
return sum;
}
Stream<int> countStream(int to) async* {
for (int i = 1; i <= to; i++) {
sleep(const Duration(milliseconds: 400));
print('publishing event $i');
yield i;
}
}
Future<void> main() async {
var stream = countStream(10);
var sum = sumStream(stream);
print('working...');
sleep(const Duration(milliseconds: 500));
print('working...');
sleep(const Duration(milliseconds: 500));
print('working...');
sleep(const Duration(milliseconds: 500));
print(await sum); // 55
}
Output:
working...
working...
working...
publishing event 1
consuming event 1
publishing event 2
consuming event 2
publishing event 3
consuming event 3
publishing event 4
consuming event 4
publishing event 5
consuming event 5
publishing event 6
consuming event 6
publishing event 7
consuming event 7
publishing event 8
consuming event 8
publishing event 9
consuming event 9
publishing event 10
consuming event 10
55
In the code above, I chose not to await for the result of sumStream() immediately because I wanted to do some additional work while sumStream() is busy consuming events from the provided stream. So my expectation was that sumStream() would start running immediately while main() is running. I expected the output to look something like below text. I expected the text working... to be interleaved with the other print outs from the publisher countStream() and the consumer sumStream().
working...
publishing event 1
consuming event 1
publishing event 2
consuming event 2
publishing event 3
consuming event 3
working...
publishing event 4
consuming event 4
publishing event 5
consuming event 5
publishing event 6
working...
consuming event 6
publishing event 7
consuming event 7
publishing event 8
consuming event 8
publishing event 9
consuming event 9
publishing event 10
consuming event 10
55
Is this because Dart is single threaded and thus it can't run main(), countStream() and sumStream() at the same time?
If so, how could I change my program so that sumStream() would run in parallel with main() (make it multi-threaded)?
I am pretty sure this is due to the sleep function. Sleep function documentation:
Use this with care, as no asynchronous operations can be processed in a isolate while it is blocked in a sleep call.
I suggest trying this instead:
await Future.delayed(const Duration(milliseconds: 500));
Which won't block the processing of all the other async operations going on.
Related
Can there be a race condition if multiple Timer/Future complete simultaneously in Dart? For example, is there a race condition when accessing the test and test structures in the Timer complete handler in the following code?
import 'dart:async';
void main() {
Map<String, int> test = {};
List<int> test2 = [];
Timer t1 = Timer(Duration(seconds: 1), () {
test['a'] = 45;
test2.add(1);
});
Timer t2 = Timer(Duration(seconds: 1), () {
test['b'] = 67;
test2.add(2);
});
Timer t3 = Timer(Duration(seconds: 2), () {
print(test);
print(test2);
});
}
Or are Timer/Future completions processed synchronously by the main thread? Can the code within two callbacks be interwoven?
Each Dart isolate executes code in a single thread. Asynchronous code running in a single Dart isolate can run concurrently but not in parallel.
In general, if the callbacks themselves do asynchronous work, then they can be interleaved. Any await (which is equivalent to any Future.then() callback) is a point where execution returns to the event loop, interrupting your asynchronous function.
In your particular example, your callbacks are fully synchronous and cannot be interrupted. Your Timers probably will fire in a defined order since events are added to FIFO queues. However, that seems brittle, and I do not think that it would be a good idea to rely on callback ordering.
Also see: Prevent concurrent access to the same data in Dart.
I have an Electron application that needs to save some data when it's closed by the user (e.g. just after the user clicked on the "Close" button).
The data is available at the renderer process, so it should be notified before the application dies.
The Electron API for Browser Window mentions a close method, but it seems this is done by the main process, not the renderer one (if I'm not mistaken).
I tried using WebContents.send from the main process to notify the renderer process, but it seems that, because the message is asynchronous, the application is closed before the renderer process has the time to actually perform the operations.
You can just use the normal unload or beforeunload events in the renderer process:
window.addEventListener('unload', function(event) {
// store data etc.
})
So far, the simplest solution that worked for me consists in doing the following:
On the main process, the BrowserWindow listens on the close event, and when it happens, it sends a message via webContents to the renderer process. It also prevents the application from being immediately closed by calling event.preventDefault();
The renderer process is always listening on IPC messages from the main process, then when it receives the close event notification, it saves its data, then sends the main process an IPC message (e.g. closed);
The main process has previously set a hook to listen to the renderer IPC messages (ipcMain.on), so when the closed message arrives, it finally closes the program (e.g. via app.quit()).
Note that, if I understood it correctly, calling app.quit() sends another close event to the BrowserWindow, so it will loop unless you prevent it somehow. I used a dirty hack (quit the second time the close event is called, without calling event.preventDefault()), but a better solution must exist.
On the Main process:
const ipc = require('electron').ipcMain;
let status = 0;
mainWindow.on('close', function (e) {
if (status == 0) {
if (mainWindow) {
e.preventDefault();
mainWindow.webContents.send('app-close');
}
}
})
ipc.on('closed', _ => {
status = 1;
mainWindow = null;
if (process.platform !== 'darwin') {
app.quit();
}
})
On the renderer process:
const electron = require('electron');
const ipc = electron.ipcRenderer;
ipc.on('app-close', _ => {
//do something here...
ipc.send('closed');
});
Simple dart problem, got a stream
Stream<Event>
I subscribe to in a unit test. Would like something like this:
stream.listen(listener)
await listenerBeenNotified5Times
expect(result,expectation)
I know expectAsync can be used to make sure the notification happens 5 times, but I want to wait to pause the execution until 5 events have been streamed.
var s = stream.take(5);
var subscription = s.listen(listener);
await susciption.asFuture;
or
await stream.take(5).toList();
In a nutshell, what's the point of having Ti.App.iOS.registerBackgroundService when you can do a similar thing with attaching a function to the "pause" event? Are there any differences between the two approaches in Titanium?
e.g.
version 1:
app.js:
service = Ti.App.iOS.registerBackgroundService({
url:"bg.js"
});
bg.js:
var sec = 0;
setInterval(function(){console.log('counting' + sec); sec = sec + 1}, 1000);
Version 2:
app.js
Titanium.App.addEventListener('pause', function(){
var sec = 0;
setInterval(function(){console.log('counting' + sec); sec = sec + 1}, 1000);
});
Version 1 & Version 2 do the exact same thing (when the app is put in the background).
Version 1 and Version 2 are not doing the same thing. Background service is a service that runs when the application is placed in the background and it will stop automatically when the application returns from background. And it can invoke Titanium.App.iOS.LocalNotification.
pause is an event which is fired when the application transitions from active to inactive state on a multitasked system. This event fires when the user leaves the application or for certain types of temporary interruptions such as a notification or incoming phone call.
From Documentation,
Note that calls to functions that modify the UI during this event may
be partially executed, up to the UI call before the suspension. See
paused event. If this happens, the remainder of the code will be
executed after the application is resumed, but before the resume event
is triggered.
Both are different and doing different jobs
I would like to launch a fairly expensive operation in response to a user clicking on a canvas element.
mouseDown(MouseEvent e) {
print("entering event handler");
var future = new Future<int>(expensiveFunction);
future.then((int value) => redrawCanvas(value);
print("done event handler");
}
expensiveFunction() {
for(int i = 0; i < 1000000000; i++){
//do something insane here
}
}
redrawCanvas(int value) {
//do stuff here
print("redrawing canvas");
}
My understanding of M4 Dart, is that this future constructor should launch "expensiveFunction" asynchronously, aka on a different thread from the main one. And it does appear this way, as "done event handler" is immediately printed into my output window in the IDE, and then some time later "redrawing canvas" is printed. However, if I click on the element again nothing happens until my "expensiveFunction" is done running from the previous click.
How do I use futures to simply launch an compute intensive function on new thread such that I can have multiple of them queued up in response to multiple clicks, even if the first future is not complete yet?
Thanks.
As mentioned in a different answer, Futures are just a "placeholder for a value that is made available in the future". They don't necessarily imply concurrency.
Dart has a concept of isolates for concurrency. You can spawn an isolate to run some code in a parallel thread or process.
dart2js can compile isolates into Web Workers. A Web Worker can run in a separate thread.
Try something like this:
import 'dart:isolate';
expensiveOperation(SendPort replyTo) {
var result = doExpensiveThing(msg);
replyTo.send(result);
}
main() async {
var receive = new ReceivePort();
var isolate = await Isolate.spawn(expensiveOperation, receive.sendPort);
var result = await receive.first;
print(result);
}
(I haven't tested the above, but something like it should work.)
Event Loop & Event Queue
You should note that Futures are not threads. They do not run concurrently, and in fact, Dart is single-threaded. All Dart code runs in an event loop.
The event loop is a loop that runs as long as the current Dart isolate is alive. When you call main() to start a Dart application, the isolate is created, and it is no longer alive after the main method is completed and all items on the event queue are completed as well.
The event queue is the set of all functions that still need to finish executing. Because Dart is single threaded, all of these functions need to run one at a time. So when one item in the event queue is completed, another one begins. The exact timing and scheduling of the event queue is something that's way more complicated than I can explain myself.
Therefore, asynchronous processing is important to prevent the single thread from being blocked by some long running execution. In a UI, a long process can cause visual jankiness and hinder your app.
Futures
Futures represent a value that will be available sometime in the Future, hence the name. When a Future is created, it is returned immediately, and execution continues.
The callback associated with that Future (in your case, expensiveFunction) is "started" by being added to the event queue. When you return from the current isolate, the callback runs and as soon as it can, the code after then.
Streams
Because your Futures are by definition asynchronous, and you don't know when they return, you want to queue up your callbacks so that they remain in order.
A Stream is an object that emits events that can be subscribed to. When you write canvasElement.onClick.listen(...) you are asking for the onClick Stream of MouseEvents, which you then subscribe to with listen.
You can use Streams to queue up events and register a callback on those events to run the code you'd like.
What to Write
main() {
// Used to add events to a stream.
var controller = new StreamController<Future>();
// Pause when we get an event so that we take one value at a time.
var subscription = controller.stream.listen(
(_) => subscription.pause());
var canvas = new CanvasElement();
canvas.onClick.listen((MouseEvent e) {
print("entering event handler");
var future = new Future<int>(expensiveFunction);
// Resume subscription after our callback is called.
controller.add(future.then(redrawCanvas).then(subscription.resume()));
print("done event handler");
});
}
expensiveFunction() {
for(int i = 0; i < 1000000000; i++){
//do something insane here
}
}
redrawCanvas(int value) {
//do stuff here
print("redrawing canvas");
}
Here we are queuing up our redrawCanvas callbacks by pausing after each mouse click, and then resuming after redrawCanvas has been called.
More Information
See also this great answer to a similar question.
A great place to start reading about Dart's asynchrony is the first part of this article about the dart:io library and this article about the dart:async library.
For more information about Futures, see this article about Futures.
For Streams information, see this article about adding to Streams and this article about creating Streams.