Skip to content

Instantly share code, notes, and snippets.

@ivanboltyshev
Last active May 29, 2025 17:36
Show Gist options
  • Save ivanboltyshev/7da431278887fa92c1801070e1af931a to your computer and use it in GitHub Desktop.
Save ivanboltyshev/7da431278887fa92c1801070e1af931a to your computer and use it in GitHub Desktop.
This utility provides a lightweight cancellation mechanism in Dart, similar to Kotlin’s Job and coroutine cancellation.
class CancellableTask {
StreamController<void>? _cancelController;
bool _isCancelled = false;
bool get isCancelled => _isCancelled;
void cancel() {
if (_isCancelled) return;
_isCancelled = true;
if (!(_cancelController?.isClosed ?? true)) {
_cancelController?.add(null);
_cancelController?.close();
}
}
Future<void> launch(Future<void> Function(CancellableTaskContext context) task) async {
cancel();
_isCancelled = false;
_cancelController = StreamController<void>.broadcast();
final context = CancellableTaskContext._(
cancelStream: _cancelController!.stream,
isCancelled: () => _isCancelled,
);
try {
await task(context);
} catch (e, st) {
if (_isCancelled) {
print('[CancellableTask] Cancelled');
} else {
rethrow;
}
} finally {
await _cancelController?.close();
}
}
}
class CancellableTaskContext {
CancellableTaskContext._({
required Stream<void> cancelStream,
required bool Function() isCancelled,
}) : _cancelStream = cancelStream,
_isCancelledFn = isCancelled;
final Stream<void> _cancelStream;
final bool Function() _isCancelledFn;
bool get isCancelled => _isCancelledFn();
Future<void> delay(Duration duration) async {
if (isCancelled) return;
final completer = Completer<void>();
final timer = Timer(duration, completer.complete);
final sub = _cancelStream.listen((_) {
if (!completer.isCompleted) {
timer.cancel();
completer.completeError(Exception('Cancelled'));
}
});
try {
await completer.future;
} finally {
await sub.cancel();
}
}
void checkCancelled() {
if (isCancelled) throw Exception('Cancelled');
}
}
class ClassExample {
CancellableTask? _task;
Future<void> call() async {
_task?.cancel();
_task = CancellableTask();
await _task?.launch((context) async {
await _jobOne();
await context.delay(const Duration(milliseconds: 2000));
await _jobTwo();
await context.delay(const Duration(milliseconds: 2000));
await _jobThree();
});
}
Future<void> _jobOne() async {
await Future.delayed(const Duration(milliseconds: 2000));
}
Future<void> _jobTwo() async {
await Future.delayed(const Duration(milliseconds: 2000));
}
Future<void> _jobThree() async {
await Future.delayed(const Duration(milliseconds: 2000));
}
}
@ivanboltyshev
Copy link
Author

CancellableTask — Kotlin-style Job cancellation in Dart

This utility provides a lightweight cancellation mechanism in Dart, similar to Kotlin’s Job and coroutine cancellation.

It allows you to:
• Execute a sequence of asynchronous operations as a single cancellable task.
• Cancel the entire operation chain at any moment with cancel().
• Automatically stop long-running steps like Future.delayed using context.delay(...).
• Check for cancellation manually using context.checkCancelled().

Problem it solves

Dart Futures cannot be cancelled once started. This utility provides a structured way to manage cancellable async flows — something Dart lacks out of the box, but is essential for user-driven workflows like voice playback, animations, and data loading where users can interrupt operations at any time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment