@Async
@Async は、@EventListener メソッドを発行の呼び出しの中ではなく、TaskExecutor 上で実行します。
使う理由
デフォルトでは、リスナーは publishEvent の中で実行されるため、発行側は処理が終わるまで待ちます。メール送信やリモートサービスの呼び出しのような遅い処理では、これは望ましくありません。注文の確定がメール送信を待つべきではないからです。@Async を付けるとリスナーは後で実行され、publishEvent はすぐに戻ります。
- コンテキストに
TaskExecutorの Bean があるときだけ有効になります。Spring の@EnableAsyncに相当する有効化の方法です。スケジューリングのスターター (spring_boot_starter_scheduling) は、applicationTaskExecutorという名前 (別名taskExecutor) で一つ登録します。エグゼキューターが一つもなければ@Asyncは無視され、リスナーは同期的に実行されます。 @Async('name')は、名前でエグゼキューターの Bean を選びます。名前がなければ、唯一の (または@Primaryの)TaskExecutorの Bean、それもなければtaskExecutorという名前の Bean を使います。- 失敗 (例外、または返した
Futureのエラー) は、マルチキャスターのエラーハンドラーに渡されます。ハンドラーがなければログに出力されます。 - 結果は、同期リスナーと同じく、得られた時点でイベントとして発行されます。
ここでの Dart にはスレッドがありません。SimpleAsyncTaskExecutor は、同じアイソレートのイベントループの後のターンでタスクを実行します。長い同期処理は、やはり他のすべてを止めてしまいます。
一つのメソッドではなくすべてのリスナーを非同期にするには、applicationEventMulticaster という名前で SimpleApplicationEventMulticaster(taskExecutor: ...) の Bean を宣言します (2 番目の例を参照)。
例
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 {
// TaskExecutor の Bean があることで @Async が有効になる。
// (spring_boot_starter_scheduling は自動で一つ登録する。)
@Bean()
TaskExecutor taskExecutor() => SimpleAsyncTaskExecutor();
}
@Component()
class Mailer {
Mailer(this.mail);
final MailSender mail;
// 発行側はメール送信を待たずに注文を確定できる。
@EventListener()
@Async()
Future<void> onPlaced(OrderPlaced order) =>
mail.send(order.customer, 'Order ${order.id} confirmed');
}
すべてのリスナーを非同期にし、エラーは投げずにログに出す例:
const log = SpringLog('neon.events');
@Configuration()
class EventConfig {
// `applicationEventMulticaster` という Bean 名でコンテキスト自身のものを置き換える。
@Bean()
ApplicationEventMulticaster applicationEventMulticaster() =>
SimpleApplicationEventMulticaster(
taskExecutor: SimpleAsyncTaskExecutor(),
errorHandler: (error, stack) =>
log.error('listener failed', error: error, stackTrace: stack),
);
}
テストでは、SimpleAsyncTaskExecutor.whenIdle は保留中のタスクがなくなると完了します。非同期リスナーの結果を確認する前に await してください。
パラメーター
| 名前 | 型 | デフォルト | 意味 |
|---|---|---|---|
value |
String? |
null |
TaskExecutor の Bean 名。null ならデフォルトのものを使う。 |
Spring (Java) との対応
@EnableAsync
@Configuration
class AsyncConfig {}
@Component
class Mailer {
@Async
@EventListener
CompletableFuture<Void> onPlaced(OrderPlaced order) { ... }
@Async("mailExecutor")
@EventListener
void onShipped(OrderShipped order) { ... }
}
重要な違い:
@EnableAsyncはありません。TaskExecutorの Bean が@Asyncを有効にします。@Asyncは@EventListenerメソッドにしか付けられません。Spring は実行時プロキシを使って任意の Bean メソッドに付けられますが、Dart には実行時プロキシがありません。それ以外の場所に付けるとジェネレーターがエラーにします。- タスクは別スレッドではなく、同じアイソレートのイベントループの後のターンで実行されます。