48

I'm on the way to evaluate Dart for a German company by porting various Java programs to Dart and compare and analyze the results. In the browser Dart wins hands down. For server software performance seemed to be a serious isssue (see this question of me) but that got mostly defused.

Now I'm in the area of porting some "simple" command-line tools where I did not expect any serious problems at all but there is at least one. Some of the tools do make HTTP requests to collect some data and the stand-alone Dart virtual machine only supports them in an asynchronous fashion. Looking through all I could find it does not seem to be possible to use any asynchronous call in a mostly synchronous software.

I understand that I could restructure the available synchronous software into an asynchronous one. But this would transform a well-designed piece of software into something less readable and more difficult to debug and maintain. For some software pieces this just does not make sense. My question: Is there an (overlooked by me) way to embed an asynchronous call into a synchronously called method?

I imagine that it would not be to difficult to provide a system call, usable only from within the main thread, which just transfers the execution to the whole list of queued asynchronous function calls (without having to end the main thread first) and as soon as the last one got executed returns and continues the main thread.

Something which might look like this:

var synchFunction() {
  var result;
  asyncFunction().then(() { result = ...; });

  resync(); // the system call to move to and wait out all async execution

  return result;
}

Having such a method would simplify the lib APIs as well. Most "sync" calls could be removed because the re-synchronisation call would do the job. It seems to be such a logical idea that I still think it somehow exists and I have missed it. Or is there a serious reason why that would not work?


After thinking about the received answer from lm (see below) for two days I still do not understand why the encapsulation of an asynchronous Dart call into a synchronous one should not be possible. It is done in the "normal" synchronous programing world all the time. Usually you can wait for a resynchronization by either getting a "Done" from the asynchronous routine or if something fails continue after a timeout.

With that in mind my first proposal could be enhanced like that:

var synchFunction() {
  var result;
  asyncFunction()
    .then(() { result = ...; })
    .whenComplete(() { continueResync() }); // the "Done" message

  resync(timeout); // waiting with a timeout as maximum limit

  // Either we arrive here with the [result] filled in or a with a [TimeoutException].
  return result;
}

The resync() does the same that would normally happen after ending the main method of an isolate, it starts executing the queued asynchronous functions (or waits for events to make them executable). As soon as it encounters a continueResync() call a flag is set which stops this asynchronous execution and resync() returns to the main thread. If no continueResync() call is encountered during the given timeout period it too aborts the asynchronous execution and leaves resync() with a TimeoutException.

For some groups of software which benefit from straight synchronous programing (not the client software and not the server software) such a feature would solve lots of problems for the programer who has to deal with asynchrounous-only libraries.

I believe that I have also found a solution for the main argument in lm's argumentation below. Therefore my question still stands with respect to this "enhanced" solution which I proposed: Is there anything which really makes it impossible to implement that in Dart?

Community
  • 1
  • 1
Tina Hildebrandt
  • 1,277
  • 1
  • 11
  • 20
  • It's not impossible, but it would require a major re-architecture of the Dart runtime philosophy, and maybe would need Dart to be multi-threaded. If they did make Dart multi-threaded then Dart programmers would all have to start dealing with thread synchronization issues which would significantly complicate everyone's life as well as breaking a lot of existing code. – bikeman868 Dec 30 '16 at 19:23
  • What you are looking for is something that blocks your code until the future returns with a result. Like while(true) loop that ends when then future return. What need next is something that fills the while(true) with a time consuming process that doesn't need to much cpu power like a sync sleep method for a certain time. So what you realy need is something like a sleepSync method. – Steffomio Jan 29 '23 at 08:00
  • simply wait for your future to complete with the sync sleep method from dart.io package in a while(true) loop. – Steffomio Jan 29 '23 at 08:06

8 Answers8

45

The only time that you can wrap an async method in a synchronous one is when you don't need to get a return value.

For example if you want to disable the save button, save results to the server asynchronously and re-enable the save button when the job is done you can write it like this:

Future<bool> save() async {
  // save changes async here
  return true;
}

void saveClicked() {
  saveButton.enabled = false;
  save()
    .then((success) => window.alert(success ? 'Saved' : 'Failed'))
    .catchError((e) => window.alert(e))
    .whenComplete(() { saveButton.enabled = true; });
}

Note that the saveClicked method is fully synchronous, but executes the save method asynchronously.

Note that if you make saveClicked async, not only do you have to call it using the async pattern, but the entire method body will run asynchronously so the save button will not be disabled when the function returns.

For completeness the async version of saveClicked looks like this:

Future<Null> saveClicked() async {
  saveButton.enabled = false;
  try {
    bool success = await save();
    window.alert(success ? 'Saved' : 'Failed');
  }
  catch (e) {
    window.alert(e);
  }
  finally {
    saveButton.enabled = true;
  }
}
bikeman868
  • 2,236
  • 23
  • 30
  • 3
    I'd argue that "turn an async method into a synchronous" is misleading. An async call is scheduled from the sync call and then just left on its own the result ignored. A sync call always stays sync anyway. It's just whether or not a callback is passed for execution to continue at a specific point when an async call completes. – Günter Zöchbauer Dec 30 '16 at 22:54
  • 2
    I agree, and maybe I can reword my answer. Since the question was about using a library of async functions in a synchronous application, I wanted to point out that if you don't care about the return value, then it is possible to wrap the async function in a synchronous one as shown. This may be helpful to someone finding this thread. – bikeman868 Dec 30 '16 at 22:59
  • I didn't downvote because I know it's not too easy to word correctly. Just thought it wouldn't hurt to point it out ;-) – Günter Zöchbauer Dec 30 '16 at 23:01
  • Thanks for improving the quality of answers. I hope the new wording is more accurate. – bikeman868 Dec 30 '16 at 23:22
  • 1
    I accept this as an answer to my question because it at least gives an idea of what is possible with the existing Dart implementation. I'm no longer involved in Dart based development for the time beeing. – Tina Hildebrandt Dec 31 '16 at 08:06
21

Yes, this is way late, but I think this is a cool feature new people should know about.

There is a way, but the Dart docs warn against it (and it's somehow "experimental", although the implications aren't really discussed).

The waitFor command.

You basically pass in an asynchronous function that returns a Future, an optional timeout parameter, and the waitFor function will return the result.

For example:

final int number = waitFor<int>(someAsyncThatReturnsInt);
Levi Lesches
  • 1,508
  • 1
  • 13
  • 23
  • 29
    Looks like you cannot use it in flutter, it's only for cli – Defuera Aug 13 '19 at 06:57
  • Yeah that's unfortunate. But there seems to be no real way to do this in Flutter (if there is I'm also interested), and OP wants cli anyway. If you really need to change async values to sync, I would suggest awaiting them in the main function, and passing that value to, say, an inherited widget. That's basically my structure for my projects. – Levi Lesches Aug 13 '19 at 17:05
  • 1
    Yes using waitFor can run Asynchronous Task, But it Blocks main Ui thread until it completed.! – dilshan Aug 04 '20 at 10:36
  • 1
    Yep -- that's what "synchronous" means.... And why working with async functions is a good thing! – Levi Lesches Aug 05 '20 at 00:20
10

The resync function cannot be implemented in Dart's current execution model.

Asynchronous execution is contagious. A synchronous function must return before any other asynchronous events can execute, so there is no way to synchronously wait for asynchronous execution.

Execution in Dart is single-threaded and event based. There is no way for the resync function to block without it also blocking all other execution in the same isolate, so the pending async operations will never happen.

To block the synchronous execution, and continue executing something else, you need to preserve the entire call stack up to that point, and reinstate it later when the synchronous operations have completed. If you have that functionality, then there are probably better ways to do things than Future and Stream :)

Also, waiting for "all async execution" isn't well-defined in an event based system. There might be a broadcast Stream emitting events coming in from the network, a periodic timer, or a receive port getting data from another isolate, or some other source of events that you can't wait for because they come from outside the isolate, or event the process. When the current isolate shuts down, it might send a final shut-down message to another isolate, so effectively the "async execution" isn't over until the isolate dies.

Using the async/await syntax, you won't get synchronous operation, but it will be easier to code the similar asynchronous operation:

function() async {
  var result = await asyncFunction();
  return result;
}

It won't wait for async operations that aren't reflected in the Future returned by asyncFunction, but that's the job of asyncFunction to not complete until its operations are complete.

lrn
  • 64,680
  • 7
  • 105
  • 121
  • 1
    The call stack argument is not convincing to me especially if you allow that only to happen in the main thread. Just continue on the existing call stack with the **resync** call. But the second argument is a real issue where no solution might exist. How to wait for the "end of all async operations" is not defined ... – Tina Hildebrandt Jan 30 '15 at 15:26
  • 1
    I'm not saying that Dart couldn't be made to work with multiple stacks (even though compilation to JS would be non-trivial), but currently there is only one stack per isolate. The async/await syntax is specified in such a way that the VM could have one stack per event, storing the stack on each await and restoring it when the future completes. It's just not done that way. And again, if we had multiple stacks, I'd want to get rid of futures completely, and just do blocking communication. – lrn Feb 02 '15 at 14:58
  • 2
    The problem with async/await is that it looks nice and seems easy to use but it is no longer possible to debug the software. And specifically for software that is inherently synchronous and uses just one or two calls to an async function it is a waste of code and resources to make almost every function asynchronous just to deal with that situation. My productivity went to zero when I was forced to do that, mainly because debugging was no longer available. – Tina Hildebrandt Feb 03 '15 at 19:43
4

Dart is inherently async. Trying to avoid asynchronity won't work out. There are sync versions of some API calls for example in dart:io and in some situations it might seem simpler to use them instead but because there aren't sync versions for all methods/functions you can't avoid async entirely.

With the recent introduction of the async/await feature programming async become much simpler and the code looks almost like sync code (but it isn't).

If a call went async it stays async. As far as I know there is nothing you can do about it.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 2
    I have proposed a solution. I still can not see why something like this could not be implemented. And yes, Dart is inherently async which is good but if there would be a simple way to support sync programming the appeal to use Dart as an universal language for many domains would be even broader. – Tina Hildebrandt Jan 30 '15 at 15:00
  • There is a simple way to program sync-only - don't call async functions ;-). Now with `async`/`await` is available I don't see why this should be a problem anyway. Async means that a callback is added to the event queue for later execution. I can't see how this could even be possible to return to sync execution in the calling function. – Günter Zöchbauer Jan 30 '15 at 15:04
  • 1
    The above proposed **resync** function could provide exactly that: move over to the event queue, work them down till empty and return. Execution would continue after resync with the guarantee that every async piece of code has been finished. – Tina Hildebrandt Jan 30 '15 at 15:09
0
import 'package:synchronized_lite/synchronized_lite.dart';

import 'dart:async';

// Using Lock as a mixin to further mimic Java-style synchronized blocks
class SomeActivity with Lock {

  bool _started = false;

  Future<bool> start() async {
    // It's correct to return a Future returned by synchronized()
    return synchronized(() async {
      if(_started)
        return false;
      // perform the start operation
      await Future.delayed(Duration(seconds: 1));
      print("Started");
      _started = true;
      return true;
    });
  }

  Future<void> stop() async {
    // It's also correct to await a synchronized() call before returning
    // It's incorrect to neither await a synchronized() call nor return its Future.
    await synchronized(() async {
      if(!_started)
        return;
      // perform the stop operation`enter code here`
      await Future.delayed(Duration(seconds: 1));
      print("Stopped");
      _started = false;
    });
  }
}

// Prints:
//   Started
//   Stopped
main() async {
  var a = SomeActivity();
  print("Hello");
  a.start();
  a.start();
  a.stop();
  await a.stop();
}
Kalyan Biswas
  • 350
  • 1
  • 3
  • 10
  • Some description of your code or why you chose this approach will improve quality of your answer. please read https://stackoverflow.com/help/how-to-answer – 32cupo Jun 12 '20 at 06:50
0

/*Since the Await statement can be used in only asynchronous methods. Then we do two methods.I thinking first we call the async method and then we constantly query the null result for the non-async method. Then we get a synchronized model. In this way, we will wait for the answer in the non-async method. Such a method comes to my mind. But as far as I can see, there is no escape from the async working model in flutter dart language. Need to get used to it.It may be unprofessional, but I wanted to share the solution that came to my mind. hope it helps.

 Stock resultStockQueryByBarcodeAsync;
  bool waitStockQueryByBarcodeAsyncCompleted = false;

  Stock WaitStockQueryByBarcodeAsync(String barcode, int timeOut) {
    CallStockQueryByBarcodeAsync(barcode);
    var startTime = new DateTime.now();
    while (!waitStockQueryByBarcodeAsyncCompleted) {
      Duration difference = DateTime.now().difference(startTime);
      if (difference.inMilliseconds > timeOut) {
        throw TimeoutException("Timeout Exceeded");
      }
      //we must scope time. Because it can be enter endless loop.
    }
    return resultStockQueryByBarcodeAsync;
  }

  void CallStockQueryByBarcodeAsync(String barcode) async {
    waitStockQueryByBarcodeAsyncCompleted = false;
    resultStockQueryByBarcodeAsync = null;
    var stock = await StockQueryByBarcodeAsync(barcode);/*your target async method*/
    waitStockQueryByBarcodeAsyncCompleted = true;
    resultStockQueryByBarcodeAsync = stock;
  }
Servet
  • 1
  • 1
0

In my case, I had to initialize the database connection from constructor. I am pretty new in Flutter and I don't know what are the best practices right now. But, here is what I did.

class Storage {
  late Database database;

  Storage() {
    getConnection().then((value) => database = value);
  }

  Future<Database> getConnection() async {
    return await openDatabase('ims.db');
  }
  
}

All I have done, is used the callback method to assign the value when the value is available.

Imran Faruqi
  • 663
  • 9
  • 19
-2

Here's a solution based on staggering the start of the async function with start times at least 1 second apart, when calls come in almost simultaneously.

Steps:

  1. Use the lastKnownTime to calculate the delta, where the initial value is 0

  2. Once the delta is not some huge number, you know it's a duplicate call.


    class StartConversationState extends State<StartConversationStatefulWidget> {

      @override
      Widget build(BuildContext context) {

        _delayPush(); // this is the call that gets triggered multiple times
      }

      int lastKnownTime = 0;
      int delayMillis = 3000;

      _delayPush() async {
        delayMillis += 1500;

        await new Future.delayed(Duration(milliseconds: delayMillis));

        int millisSinceEpoch = new DateTime.now().millisecondsSinceEpoch;
        int delta = millisSinceEpoch - lastKnownTime;

        // if delta is less than 10 seconds, means it was a subsequent interval
        if (delta < 10000) {

          print('_delayPush() , SKIPPING DUPLICATE CALL');
          return;
        }

        // here is the logic you don't want to duplicate
        // eg, insert DB record and navigate to next screen
    }

Gene Bo
  • 11,284
  • 8
  • 90
  • 137