@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.
- It takes effect only when the context has a
TaskExecutorbean. This is how it is switched on, as@EnableAsyncdoes in Spring. The scheduling starter (spring_boot_starter_scheduling) registers one, namedapplicationTaskExecutorwith the aliastaskExecutor. Without any executor,@Asyncis ignored and the listener runs synchronously. @Async('name')picks an executor bean by name. Without a name, the context uses the only (or@Primary)TaskExecutorbean, else the one namedtaskExecutor.- A failure (thrown, or from the returned
Future) goes to the multicaster's error handler, else it is logged. - A result is published as an event when it arrives, as for synchronous listeners.
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:
- There is no
@EnableAsync. ATaskExecutorbean switches@Asyncon. @Asyncis accepted on@EventListenermethods only. Spring puts it on any bean method through a runtime proxy, and Dart has no runtime proxies. The generator rejects it elsewhere.- Tasks run in a later event-loop turn of the same isolate, not on another thread.