@Async

@Async runs an @EventListener method on a TaskExecutor instead of inside the publishing call.

Why use it

By default a listener runs inside publishEvent, so the publisher waits until it is done. That is wrong for slow work such as sending a mail or calling a remote service: placing an order should not wait for the mail. With @Async the listener runs later, and publishEvent returns at once.

Dart has no threads here. SimpleAsyncTaskExecutor runs the task in a later turn of the event loop of the same isolate. A long synchronous task still blocks everything else.

To make every listener asynchronous instead of one method, declare a SimpleApplicationEventMulticaster(taskExecutor: ...) bean named applicationEventMulticaster (see the second example).

Example

import 'package:spring_core_dart/spring_core_dart.dart';

class OrderPlaced {
  const OrderPlaced(this.id, this.customer);
  final String id;
  final String customer;
}

abstract interface class MailSender {
  Future<void> send(String to, String subject);
}

@Configuration()
class AsyncConfig {
  // Having a TaskExecutor bean is what switches @Async on.
  // (spring_boot_starter_scheduling registers one for you.)
  @Bean()
  TaskExecutor taskExecutor() => SimpleAsyncTaskExecutor();
}

@Component()
class Mailer {
  Mailer(this.mail);
  final MailSender mail;

  // The order is confirmed without the publisher waiting for the mail.
  @EventListener()
  @Async()
  Future<void> onPlaced(OrderPlaced order) =>
      mail.send(order.customer, 'Order ${order.id} confirmed');
}

All listeners asynchronous, with errors logged instead of thrown:

const log = SpringLog('neon.events');

@Configuration()
class EventConfig {
  // The bean name `applicationEventMulticaster` replaces the context's own.
  @Bean()
  ApplicationEventMulticaster applicationEventMulticaster() =>
      SimpleApplicationEventMulticaster(
        taskExecutor: SimpleAsyncTaskExecutor(),
        errorHandler: (error, stack) =>
            log.error('listener failed', error: error, stackTrace: stack),
      );
}

In tests, SimpleAsyncTaskExecutor.whenIdle completes once no task is pending. Await it before you check what an asynchronous listener did.

Parameters

Name Type Default Meaning
value String? null Name of the TaskExecutor bean. null uses the default one.

In Spring (Java)

@EnableAsync
@Configuration
class AsyncConfig {}

@Component
class Mailer {
  @Async
  @EventListener
  CompletableFuture<Void> onPlaced(OrderPlaced order) { ... }

  @Async("mailExecutor")
  @EventListener
  void onShipped(OrderShipped order) { ... }
}

Differences that matter:

See also