Queue and Completer with dart async

Photo by Levi Jones on Unsplash

Queue and Completer with dart async

this is my coding studying minutes of Queue & Completer in Dart and how to use it.

use case:

I am trying to build an advanced Ble Queue manager that sends data to multiple devices simultaneously and need to understand how Completer and Queue work in depth to have a customized and powerful solution.

What is Queue in Dart? and Why use it?

  • it is a special kind of Iterator collection that can be manipulated at both ends.

  • you can add and remove to each end of the stack only (first of the queue and last, no index items access or manipulation).

  • it can have only one manipulation done at a time, not allowed to modify the queue (add or remove entries) while an operation in the queue is being performed.

  • it is useful to Implement FIFO 'First in First out ' or LIFO 'Last in First out ' techniques to a list of items or operations using 'removeFirst' & 'removeLast' methods.

  • It is much more efficient than a list because adding or removing items at the beginning of the list is an O(n) operation because it requires shifting all the other elements. With a Queue, adding or removing items at both ends is an O(1) operation, which can be significantly faster for large collections.

Queue Example :

final queue = Queue<int>(); // ListQueue() by default
print(queue.runtimeType); // ListQueue

// Adding items to queue
queue.addAll([1, 2, 3]);
queue.addFirst(0);
queue.addLast(10);
print(queue); // {0, 1, 2, 3, 10}

// Removing items from queue
queue.removeFirst();
queue.removeLast();
print(queue); // {1, 2, 3}

What is Completer in Dart? and Why use it?

  • a Completer in Dart is a way to produce Future objects and to complete them later at any point in time.

  • it has a future object, this will not complete or run until we call Completer. Complete on it

  • the Completer has

    • a Completer.complete method to complete the future and return the result

    • a Completer.completeError method to return an error if the item failed.

  • u can add callbacks to the end of it to be executed once completed.

  • it is useful when you're dealing with asynchronous operations that need to be executed in a certain order (like a BLE Queue ).

    Completer example:

      var completer = Completer<String>();
    
      // Get the future provided by the completer.
      var future = completer.future;
    
      // You can now add callbacks to the future.
      future.then((value) => print('Received: $value'));
    
      // At some point in the future, you can complete the completer.
      // This will cause the future to complete with the value,
      //  and the callback will be called.
      completer.complete('Hello, world!');
    

    In this example, when completer.complete('Hello, world!') is called, the future immediately completes with the value 'Hello, world!', and 'Received: Hello, world!' is printed.

Simple TaksQueue examaple :

to make a simple task Queue that executes futures we need to :

  • have Queue<Completer> to hold the items and a method to run each item by calling complete method on it.

  • we have add method to add a task to the queue and process it

  • we have an _running bool to skip processing if an item is processed already.

  • call _run on the item that needs to be processed that calls the Completer.complete method.

  • then catch any errors or return the value depending on the result of the future.

  • then remove the task from the queue and start processing the next one.

  • if we want to have a pause feature for the queue we need :

    • to have a paused bool to determine the state of the queue

    • check if the queue is paused before processing any item and return without execution if it is.

    • add resume method to switch paused value and re-process the queue.

here is an example of this Queue logic :

class TaskQueue {
  final _queue = Queue<Completer<void>>();
  bool _running = false;
  bool _paused = false;

  Future<void> add(Future<void> Function() task) {
    final completer = Completer<void>();
    _queue.add(completer);
    _run(task, completer);
    return completer.future;
  }

  void pause() {
    _paused = true;
  }

  void resume() {
    _paused = false;
    if (_queue.isNotEmpty) {
      _run(_queue.first, _queue.removeFirst());
    }
  }

  Future<void> _run(Future<void> Function() task, Completer<void> completer) async {
    if (_running || _paused) return;
    _running = true;

    try {
      await task();
      completer.complete();
    } catch (e) {
      completer.completeError(e);
    } finally {
      _queue.remove(completer);
      _running = false;
      if (!_paused && _queue.isNotEmpty) {
        _run(_queue.first, _queue.removeFirst());
      }
    }
  }
}

that's it for now, you can add extra functionality to the queue as you want and share with me your inputs.