Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dart File.writeAsString() method does not write to file if await is not done immediately

Tags:

dart

I have the following Dart code that doesn't behave as I expected:

final File file = File("result.csv");

Future send(String message) async {
  try {
    await file.writeAsString(message + '\n',
        mode: FileMode.append, flush: true);
  } catch (e) {
    print("Error: $e");
  }
  return await file.length();
}

main() async {
  final futures = <Future>[];
  for (int i = 0; i < 100; i++) {
    futures.add(send("$i"));
  }
  for (int i = 0; i < 100; i++) {
    print(await futures[i]);
  }
}

I expected the file to be written as soon as each call to await futures[i] in the second loop returned. However this does not seem to be happening.

The file should contain one line for each index from 0 to 99. But it contains a line with 99 followed by an empty line. And the print calls in the second loop always print the same file length, 3.

The event loop seems to be somehow merging the calls and only actually executing the last call, even though I still get 100 different futures that I await in the second loop.

Why is this happening and how can I allow the futures to run without awaiting them immediately (I really need to await only later, when all of the calls to send have been made)?

like image 767
Renato Avatar asked Oct 20 '25 05:10

Renato


1 Answers

With the loop:

for (int i = 0; i < 100; i++) {
  futures.add(send("$i"));
}

multiple send are immediately invoked and each one concurrently open and write a string in the file: you have a race condition and at the ends it happens that only one message it is written to the file.

Use a list of functions that return a future

With a closure it is possible to implement a sequenzialized version for file access that avoid the race condition.

Instead of creating a list of futures, create a list of functions that returns a future: The shared file resource is accessed sequentially if you call and await such functions in a loop:

import 'dart:io';

final File file = File("result.csv");

typedef Future SendFunction();

Future send(String message) async {
  try {
    await file.writeAsString(message + '\n',
        mode: FileMode.append, flush: true);
  } catch (e) {
    print("Error: $e");
  }
  var l = await file.length();
  return l;
}

main() async {

  final futures = List<SendFunction>();
  //final futures = <Future>[];

  for (int i = 0; i < 100; i++) {
    //futures.add(send("$i"));
    futures.add(() => send("$i"));
  }

  for (int i = 0; i < 100; i++) {
    print(await futures[i]());
    //print(await futures[i]);
  }
}

synchronized package

synchronized offers lock mechanism to prevent concurrent access to asynchronous code.

In this case it may be used to avoid concurrent access to result.csv file:

import 'dart:io';
import 'package:synchronized/synchronized.dart';

final File file = File("result.csv");
final lock = new Lock();

Future send(String message) async {
  try {
    await file.writeAsString(message + '\n',
        mode: FileMode.append, flush: true);
  } catch (e) {
    print("Error: $e");
  }
  return await file.length();
}

main() async {
  final futures = <Future>[];
  for (int i = 0; i < 100; i++) {
    futures.add(lock.synchronized(() => send("$i")));
  }
  for (int i = 0; i < 100; i++) {
    print(await futures[i]);
  }
}
like image 165
attdona Avatar answered Oct 22 '25 05:10

attdona