async-await-null-race-condition

Par divinevideo · divine-mobile

Corriger les erreurs « Null check operator used on a null value » lorsqu'un objet est mis à null pendant un async await. À utiliser quand : (1) la référence de l'objet est annulée pendant l'attente, (2) le code accède à l'objet avec ! après le retour de l'await, (3) des opérations d'annulation/de destruction s'exécutent en parallèle avec des opérations asynchrones sur le même objet. Solution : capturer une référence locale avant l'await.

npx skills add https://github.com/divinevideo/divine-mobile --skill async-await-null-race-condition

Course de condition Async Await Null

Problème

Quand une opération asynchrone est en attente, du code externe peut définir la référence d'objet à null. Quand l'await se termine, accéder à l'objet avec ! lève "Null check operator used on a null value".

Contexte / Conditions déclenchantes

  • Erreur : Null check operator used on a null value
  • Modèle : await someObject!.asyncMethod() suivi de someObject!.property
  • L'objet peut être annulé par des opérations d'annulation/disposition/nettoyage
  • Opérations concurrentes sur un état mutable partagé
  • Généralement visible avec les objets de session, les gestionnaires de connexion, ou les références de service

Solution

Incorrect - L'objet peut devenir null pendant l'await :

Future<Result> waitForResponse() async {
  // _session pourrait être défini à null pendant qu'on attend
  final result = await _session!.waitForConnection();

  if (result == null) {
    // BUG: _session pourrait être null ici !
    final state = _session!.state;  // Lève une erreur de null check
    ...
  }
}

void cancel() {
  _session?.cancel();
  _session = null;  // Cela s'exécute pendant que waitForResponse attend
}

Correct - Capturer la référence locale avant l'await :

Future<Result> waitForResponse() async {
  // Capturer la référence AVANT l'await
  final session = _session!;

  final result = await session.waitForConnection();

  // Vérifier si annulé pendant l'await
  if (_session == null) {
    return Result.cancelled();
  }

  if (result == null) {
    // Sécurisé - utilise la référence locale capturée
    final state = session.state;
    ...
  }
}

Vérification

  1. Déclencher l'opération d'annulation/disposition pendant que l'opération asynchrone est en cours
  2. Aucune erreur de null check ne devrait se produire
  3. L'opération devrait gérer l'annulation de manière gracieuse

Exemple - Modèle complet

class ConnectionManager {
  Session? _session;

  Future<ConnectionResult> connect() async {
    if (_session == null) {
      return ConnectionResult.failure('No session');
    }

    // Capturer avant l'await
    final session = _session!;

    final response = await session.waitForResponse(
      timeout: Duration(minutes: 2),
    );

    // Vérifier si annulé pendant l'await
    if (_session == null) {
      return ConnectionResult.failure('Cancelled');
    }

    // Sécurisé d'utiliser la référence capturée
    if (response == null) {
      return ConnectionResult.failure(session.errorMessage);
    }

    return ConnectionResult.success(response);
  }

  void cancel() {
    _session?.cancel();
    _session?.dispose();
    _session = null;
  }
}

Notes

  • Ce modèle s'applique à toute référence mutable qui peut être annulée de l'extérieur
  • Courant dans Flutter/Dart avec les modèles de disposition, les providers Riverpod, et les sessions WebSocket
  • La référence locale capturée maintient l'objet vivant pendant l'await
  • Toujours ajouter une vérification null après l'await pour détecter l'annulation
  • Considérer l'utilisation de modèles d'annulation Completer pour des scénarios plus complexes

Modèles connexes

  • riverpod-ref-read-in-dispose - Problème similaire avec le cycle de vie de la ref Riverpod
  • flutter-dispose-timer-test-failure - Callbacks de timer après disposition

Skills similaires