Why Future block the ui but network requests don't in flutter - flutter

I know Future will run in event queue.But event queue are also running on main isolate, if i do some heavy task (for example, calculate sum from 1 to 1000000) in future, it will block my ui code.
But Future in network operation will not block ui (such as await httpClient.getUrl(uri)).
Why does a network request using future take several seconds without blocking the UI, while computational operations block the UI?
#override
void initState() {
super.initState();
Future((){
var result;
for (var i = 0; i < 1000000; ++i) {
result = 'result is $i';
}
print(result);
});
}
if i do some heavy task using Future in initState(), the ui will be blocked.

Isolates in Dart are single-threaded. An isolate can do only one thing at a time.
Asynchronous functions are basically a form of cooperative multitasking. A function must yield (usually via await) to allow other operations to execute in the isolate.
Your computation doesn't yield, so it must run in its entirety before the UI can resume processing events, and the UI will be unresponsive. If you altered it:
Future(() async {
var result;
for (var i = 0; i < 1000000; ++i) {
result = 'result is $i';
await Future.delayed(Duration.zero);
}
print(result);
});
then you should find that the UI can process events regularly and should have the appearance of remaining responsive. (Note that your computation will take much longer to complete because of the additional extra overhead.)

Let me answer briefly, the network request (HttpClient in dart:io) actually ended up in another isolate.
find _NativeSocket section inside socket_patch.dart file, keep searching down and you will see this statement (the link is likely to point to the wrong line as the SDK is constantly updated in the future):
_EventHandler._sendData(this, eventPort!.sendPort, fullData);
Does it look familiar?

Related

When to use Dart/Flutter Isolate?

I read about dart concurrency. CMIIW, we should use isolate to not block UI render in flutter. But, I found that isolate.spawn is significantly slower.
So, why we should isolate? isn't it better to just use the main than to wait a whole lot longer?
import 'dart:convert';
import 'dart:isolate';
void main() async {
final jsonStr = '{'
'"name": "alif",'
'"age": 26,'
'"gender": "male",'
'"office": "work from home"'
'}';
Future asyncRun<Q>(Function(Q) function, Q message) async {
function(message);
}
final stopwatchA = Stopwatch()..start();
for (var i = 0; i < 100; i++) {
jsonDecode(jsonStr);
}
print('100 sync run : ${stopwatchA.elapsed}');
final stopwatchB = Stopwatch()..start();
await Future.wait(
[for (var i = 0; i < 100; i++) asyncRun(jsonDecode, jsonStr)],
);
print('100 async run : ${stopwatchA.elapsed}');
final stopwatchC = Stopwatch()..start();
await Future.wait(
[for (var i = 0; i < 100; i++) Isolate.spawn(jsonDecode, jsonStr)],
);
print('100 isolate.spawn: ${stopwatchC.elapsed}');
}
after i read comment from jamesdlin about spawn time.
I update the code. I expand the json and use only a single isolate. I found the isolate run much faster. But I don't understand, why is that happened? I expect slightly worse or similar time at best.
import 'dart:convert';
import 'dart:isolate';
void main() async {
final N = 1000;
String content = '';
for (var i = 0; i < N; i++) {
content += '"content$i": $i';
if (i != N - 1) content += ',';
}
final jsonStr = '{$content}';
Future asyncRun<Q>(Function(Q) function, Q message) async {
function(message);
}
final stopwatchA = Stopwatch()..start();
jsonDecode(jsonStr);
print('$N content sync run : ${stopwatchA.elapsed}');
final stopwatchB = Stopwatch()..start();
await asyncRun(jsonDecode, jsonStr);
print('$N content async run : ${stopwatchA.elapsed}');
final stopwatchC = Stopwatch()..start();
await Isolate.spawn(jsonDecode, jsonStr);
print('$N content isolate run : ${stopwatchC.elapsed}');
}
When do somthing which use a lot of cpu time,
if it run on main isolate, the UI will blocked.
isolate.spawn is the best way.
Using isolates, your Dart code can perform multiple independent tasks at once, using additional processor cores if they're available. Isolates are like threads or processes, but each isolate has its own memory and a single thread running an event loop.
The main isolate
You often don’t need to think about isolates at all. A typical Dart app executes all its code in the app’s main isolate,
Even single-isolate programs can execute smoothly by using async-await to wait for asynchronous operations to complete before continuing to the next line of code. A well-behaved app starts quickly, getting to the event loop as soon as possible. The app then responds to each queued event promptly, using asynchronous operations as necessary.
The isolate life cycle
As the following figure shows, every isolate starts by running some Dart code, such as the main() function. This Dart code might register some event listeners—to respond to user input or file I/O, for example. When the isolate’s initial function returns, the isolate stays around if it needs to handle events. After handling the events, the isolate exits.
Event handling
In a client app, the main isolate’s event queue might contain repaint requests and notifications of tap and other UI events. For example, the following figure shows a repaint event, followed by a tap event, followed by two repaint events. The event loop takes events from the queue in first in, first out order.
Official Documentation

Dart future blocking main thread

I'm working on an app that captures and processes an image. A simplified version of the code is:
build() {
return FloatingActionButton(
onPressed: processImage,
child: Icon(
Icons.camera_alt,
color: color,
),
);
}
processImage(Camera image) async {
await image.process();
}
And in another class:
Future<image> process() {
return Future(() {
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
//process image
}
}
});
}
But when process() is running, the UI freezes.
Why is this happening? Isn't that function passed to Future constructor running in the background?
As Dart uses an event loop, all code (synchronous and asynchronous) will simply be run on the same isolate (think thread in other languages as an analogy), just at different points in time. As such, when your process method is dequeued and executed, it will block the thread and cause frames to be dropped due to a longer execution time, despite being asynchronous. The optimal solution to the problem is to spawn another isolate in a new thread, and carry out the computation there. Flutter provides a convenience method for this exact use case, called compute. It takes a top-level function (not in a class, nor anonymous) that can have a primitive type parameter (including Map and List) as an argument and will return at some point in the future. For more information on compute, see its documentation linked above.
If you have multiple parameters that you need to pass to compute, a common pattern (outside of just this use case) is making a method that serializes a class' fields to a Map<String, dynamic>, and a factory constructor that creates an object from a Map<String, dynamic>. This process would be easier with reflection, but Flutter disables it due to performance reasons.
For a full example on compute from the Flutter documentation, see here: https://flutter.dev/docs/cookbook/networking/background-parsing
You can insert the gap into the event loop.
Simple way:
Future<image> process2() {
return Future(() async {
for (var x = 0; x < width; x++) {
for (var y = 0; y < height; y++) {
// process
}
if (x % 100 == 0) {
await Future.delayed(Duration(seconds: 100));
}
}
});
}

How to communicate with an isolate performing a blocking operation in Dart/Flutter?

In a work for desktop flutter application (linux), I need to do some intensive computing task into an isolate. But, while this isolate is performing this long operation, it is not reading incoming messages (which seems logic).
I would like to still be able to communicate with it while it is performing.
ReceivePort rPort;
SendPort sPort;
Isolate isolate;
void main() {
rPort = ReceivePort();
isolate = await Isolate.spawn(entryPoint, receivePort.sendPort);
sPort = await rPort.first;
sendMessage("perform"); // this is being processed
sendMessage("controlMessage"); // this is being processed after the perform has ended
}
void sendMessage(String msg) {
ReceivePort localReceivePort = ReceivePort();
sendPort.send([msg, localReceivePort.sendPort]);
}
void entryPoint(SendPort sendPort)
{
ReceivePort receivePort = ReceivePort();
sendPort.send(receivePort.sendPort);
receivePort.listen((message) {
String data = msg[0];
SendPort reply = msg[1];
print(data);
if(data == "perform") performBlockingOperation();
else if(data == "controlMessage") controlPerformance();
repy.send("something");
});
}
I tried many options : making the performBlockingOperation async, but this doesn't seem to work.
Even trying to create a native thread inside the isolate returns
../../third_party/dart/runtime/vm/runtime_entry.cc: 3331: error:
Cannot invoke native callback outside an isolate.
Is there a way to achieve what I am trying ?
Making the computation asynchronous is the right first step.
The second step is to actually yield control at some points during the computation.
Then insert some await Future.delayed(Duration.zero); statements in your computation code that is blocking everything else. Not too deep in the computation, because it does introduce a delay, but often enough that your isolate gets a chance to check for new events occasionally.

Is there a way for Flutter's Timer.periodic to wait for a function return plus a fixed duration before moving to the next cycle?

I use Timer.periodic with a duration normally. This works perfectly for my current use case.
Duration fiveSecs = const Duration(seconds: 5);
new Timer.periodic(fiveSecs, checkChange);
void checkChange(Timer timer) async {
//do some network calls
}
In this particular case I make network calls that take no longer than 500ms, and doing them every 5 seconds is enough for whatever depends on what those calls return.
Now I want to check for new values as frequently as 2 seconds, however, based on what needs to be checked a network call could take anywhere from a few hundred milliseconds to even 10 seconds. I could give a large margin and use a frequency of like 20 seconds for the timer, but in this case I would even prefer a 1 second frequency if possible.
So is there a way the timer could wait for a function, and still have a fixed duration like 1 second. So the maximum time it would take would be 1 second + callback runtime? Or is there a better way this could be achieved, I'm open to suggestions. Thank you.
void startTimer() {
_timer = Timer.periodic(Duration(seconds: 1), (Timer t) async {
print("lets wait for 5 seconds");
_timer.cancel();
await Future.delayed(Duration(seconds: 5));
print("Job is done");
print(DateTime.now());
print("Do it again");
startTimer();
});
}
I have encountered the same situation lately,
what I did was (in my case inside a static class) to add a static boolean value and toggle it accoding to my situation needs, later checking it's value inside the Timer.Periodic callback.
Timer.periodic(ConstantValues.socketTimerOperationDelay, (Timer t) async{
if(_udpHandlerCompleted){
_udpHandlerCompleted = false;
if(!_shouldSendBroadcast || _shutDown)
t.cancel();
else
_writeAllToUdpSocket(_udpSocket, data, ConstantValues.udpMulticastGroup, ConstantValues.udpMulticastPort);
_udpHandlerCompleted = true;
}
});
As you can see in my situation, I had to wait for the callback to end before I move to the next one, I believe this is what you're looking for, but in case you need to await for a different method the solution would be similar,
simply toggle the boolean _udpHandlerCompleted (in my case) in the other function.
Edit:
If my soultion helped you, kindly mark it as the accepted answer. Good luck!
At the end checkChange add Future.delayed(const Duration(seconds: 5), checkChange) then call it once instead of running the timer. You can add/check a boolean flag if you need to kill it at any point.

Best practices Implementing ProgressMonitorDialog in Eclipse RCP

I wish to Show progress of a long running operation(process) in UI, so that the user can understand the status of the background job. This is the way I have implemented and I feel like the code is absurd. Below is my code
dialog.run(true,false, new IRunnableWithProgress() {
#Override
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
monitor.beginTask("Main process", 10);
for (int i = 0; i < 10; i++) {
if (monitor.isCanceled()) return;
monitor.subTask("Status message");
sleep(1000);
// worked increases the monitor, the values are added to the existing ones
monitor.worked(1);
if(i == 3) {
sleep(3000);
callMe();//calling a long running function
}
if(i == 9) {
monitor.subTask("finishing setup..... please wait ");
sleep(2000);
}
}
monitor.done();
}
});
Note: There is a sleep method somewhere in the code
here at i == 3 an operation/function is called that takes a minimum of 5 minutes, post execution of the function the progress continues.
I don't want the progress to be stopped while executing the function(long running operation) rather progress must be shown even while executing it.
can someone show the correct programming practices in showing progress
The reason your code feels absurd is that wrapping the long-running method in a IRunnableWithProgress.run() really does not add much in itself, there is no magic.
To make the ProgressMonitorDialog (or e.g. the related Job API) work for you, you need to change "callMe()" so it takes "IProgressMonitor monitor" as a parameter, and use that to listen for cancel-requests, and use it also for reporting progress.
To say the same again, using different wording: You need to recursively add "IProgressMonitor monitor" as a parameter to all long-running method calls. All long-running operations must be build with this (IProgressMonitor) in mind if any progress is to be reported and/or you want it to be cancelable.