Scheduler Reference Guide
现代应用程序通常需要定期运行特定任务。Quarkus 中有以下两个调度扩展。 quarkus-scheduler`扩展带来了 API 和一个轻量级的内存内调度器实现。 `quarkus-quartz`扩展从 `quarkus-scheduler`扩展中实现了 API,并且包含了一个基于 Quartz 库的调度器实现。只有对于高级调度用例(比如持久任务和集群)才需要 `quarkus-quartz
。
Modern applications often need to run specific tasks periodically.
There are two scheduler extensions in Quarkus.
The quarkus-scheduler
extension brings the API and a lightweight in-memory scheduler implementation.
The quarkus-quartz
extension implements the API from the quarkus-scheduler
extension and contains a scheduler implementation based on the Quartz library.
You will only need quarkus-quartz
for more advanced scheduling use cases, such as persistent tasks and clustering.
如果向项目添加了 `quarkus-quartz`依赖项,来自 `quarkus-scheduler`扩展的轻量级调度器实现将自动禁用。 |
If you add the |
Scheduled Methods
使用 @io.quarkus.scheduler.Scheduled`进行注解的方法将自动安排调用。经过调度的函数不得是抽象方法或私有方法。它可以是静态或非静态方法。可以对经过调度的函数使用拦截器绑定进行注解,比如 `@jakarta.transaction.Transactional`和 `@org.eclipse.microprofile.metrics.annotation.Counted
。
A method annotated with @io.quarkus.scheduler.Scheduled
is automatically scheduled for invocation.
A scheduled method must not be abstract or private.
It may be either static or non-static.
A scheduled method can be annotated with interceptor bindings, such as @jakarta.transaction.Transactional
and @org.eclipse.microprofile.metrics.annotation.Counted
.
如果有某个 bean 类没有作用域,并且声明至少一个使用 |
If there is a bean class that has no scope and declares at least one non-static method annotated with |
此外,进行注解的方法必须返回 void
,并且不声明参数或声明一个 `io.quarkus.scheduler.ScheduledExecution`类型的参数。
Furthermore, the annotated method must return void
and either declare no parameters or one parameter of type io.quarkus.scheduler.ScheduledExecution
.
该注解可重复,因此一个方法可以多次调度。 |
The annotation is repeatable so a single method could be scheduled multiple times. |
Inheritance of metadata
子类绝不会继承在超类中声明的 `@Scheduled`方法的元数据。比如,假设类 `org.amce.Foo`由类 `org.amce.Bar`扩展。如果 `Foo`声明一个使用 `@Scheduled`进行注解的非静态方法,那么 `Bar`将不会继承经过调度的函数的元数据。在以下示例中,仅对 `Foo`的实例调用 `everySecond()`函数。
A subclass never inherits the metadata of a @Scheduled
method declared on a superclass.
For example, suppose the class org.amce.Foo
is extended by the class org.amce.Bar
.
If Foo
declares a non-static method annotated with @Scheduled
then Bar
does not inherit the metadata of the scheduled method.
In the following example, the everySecond()
method is only invoked upon the instance of Foo
.
class Foo {
@Scheduled(every = "1s")
void everySecond() {
// ..do something
}
}
@Singleton
class Bar extends Foo {
}
CDI events
当发生特定事件时,某些 CDI 事件将同步、异步触发。
Some CDI events are fired synchronously and asynchronously when specific events occur.
Type | Event description |
---|---|
|
An execution of a scheduled job completed successfuly. |
|
An execution of a scheduled job completed with an exception. |
|
An execution of a scheduled job was skipped. |
|
The scheduler was paused. |
|
The scheduler was resumed. |
|
A scheduled job was paused. |
|
A scheduled job was resumed. |
Triggers
触发器由 `@Scheduled#cron()`或 `@Scheduled#every()`属性定义。如果两个都已指定,则 cron 表达式优先。如果没有指定,则构建将因 `IllegalStateException`失败。
A trigger is defined either by the @Scheduled#cron()
or by the @Scheduled#every()
attribute.
If both are specified, the cron expression takes precedence.
If none is specified, the build fails with an IllegalStateException
.
CRON
CRON 触发器由类 cron 的表达式定义。比如,`"0 15 10 * * ?"`每天上午 10:15 触发。
A CRON trigger is defined by a cron-like expression.
For example "0 15 10 * * ?"
fires at 10:15am every day.
@Scheduled(cron = "0 15 10 * * ?")
void fireAt1015AmEveryDay() { }
CRON 表达式中使用的语法由 quarkus.scheduler.cron-type`属性控制。这些值可以是 `cron4j
、quartz
、unix`和 `spring
。默认情况下使用 quartz
。
The syntax used in CRON expressions is controlled by the quarkus.scheduler.cron-type
property.
The values can be cron4j
, quartz
, unix
and spring
.
quartz
is used by default.
`cron`属性支持 Property Expressions,包括默认值和嵌套 Property Expressions。(请注意,仍然支持“{property.path}”样式的表达式,但这些表达式不能提供 Property Expressions 的全部功能。)
The cron
attribute supports Property Expressions including default values and nested
Property Expressions. (Note that "{property.path}" style expressions are still supported but don’t offer the full functionality of Property Expressions.)
@Scheduled(cron = "${myMethod.cron.expr}")
void myMethod() { }
如果你希望禁用一个特定的已调度方法,则可以将它的 cron 表达式设置成 "off"`或 `"disabled"
。
If you wish to disable a specific scheduled method, you can set its cron expression to "off"
or "disabled"
.
myMethod.cron.expr=disabled
Property Expressions 允许你定义一个默认值,如果属性未配置,将使用该值。
Property Expressions allow you to define a default value that is used, if the property is not configured.
0 0 15 ? * MON *
@Scheduled(cron = "${myMethod.cron.expr:0 0 15 ? * MON *}")
void myMethod() { }
如果属性 myMethod.cron.expr
未定义或为 null
,将使用默认值 (0 0 15 ? * MON *
)。
If the property myMethod.cron.expr
is undefined or null
, the default value (0 0 15 ? * MON *
) will be used.
Time Zones
cron 表达式是在默认时区的上下文中计算的。然而,也可以将 cron 表达式与特定时区关联。
The cron expression is evaluated in the context of the default time zone. However, it is also possible to associate the cron expression with a specific time zone.
@Scheduled(cron = "0 15 10 * * ?", timeZone = "Europe/Prague") 1
void myMethod() { }
1 | The time zone ID is parsed using java.time.ZoneId#of(String) . |
timeZone
属性支持 Property Expressions,包括默认值和 nestedProperty 表达式。
The timeZone
attribute supports Property Expressions including default values and nested
Property Expressions.
@Scheduled(cron = "0 15 10 * * ?", timeZone = "{myMethod.timeZone}")
void myMethod() { }
Intervals
时间间隔触发器定义了调用之间的间隔。间隔表达式基于 ISO-8601 持续时间格式 PnDTnHnMn.nS
,并使用 java.time.Duration#parse(CharSequence)
解析 @Scheduled#every()
的值。但是,如果表达式以数字开头并以 d
结尾,将自动添加 P
前缀。如果表达式仅以数字开头,将自动添加 PT
前缀。因此,例如,可以使用 15m
代替 PT15M
,并解析为“15 分钟”。
An interval trigger defines a period between invocations.
The period expression is based on the ISO-8601 duration format PnDTnHnMn.nS
and the value of @Scheduled#every()
is parsed with java.time.Duration#parse(CharSequence)
.
However, if an expression starts with a digit and ends with d
, P
prefix will be added automatically. If the expression only starts with a digit, PT
prefix is added automatically.
So for example, 15m
can be used instead of PT15M
and is parsed as "15 minutes".
@Scheduled(every = "15m")
void every15Mins() { }
小于 1 秒的值可能不受底层调度程序实现支持。在这种情况下,将在生成和应用程序启动期间记录一条警告消息。
A value less than one second may not be supported by the underlying scheduler implementation. In that case a warning message is logged during build and application start.
every
属性支持 Property Expressions,包括默认值和 nestedProperty 表达式。(请注意,"{property.path}"
样式表达式仍然受支持,但不提供 Property 表达式的全部功能。)
The every
attribute supports Property Expressions including default values and nested
Property Expressions. (Note that "{property.path}"
style expressions are still supported but don’t offer the full functionality of Property Expressions.)
@Scheduled(every = "${myMethod.every.expr}")
void myMethod() { }
可以通过将间隔值设置为 "off"
或 "disabled"
来禁用间隔。例如,可以使用具有默认值 "off"
的 Property 表达式,如果其 Config Property 尚未设置,则禁用触发器。
Intervals can be disabled by setting their value to "off"
or "disabled"
.
So for example a Property Expression with the default value "off"
can be used to disable the trigger if its Config Property has not been set.
@Scheduled(every = "${myMethod.every.expr:off}")
void myMethod() { }
Identity
默认情况下,将为每个已计划的方法生成一个唯一标识符。此标识符用于日志消息、调试期间以及作为某些 io.quarkus.scheduler.Scheduler
方法的参数。因此,指定显式标识符的可能性可能很有用。
By default, a unique identifier is generated for each scheduled method.
This identifier is used in log messages, during debugging and as a parameter of some io.quarkus.scheduler.Scheduler
methods.
Therefore, a possibility to specify an explicit identifier may come in handy.
@Scheduled(identity = "myScheduledMethod")
void myMethod() { }
identity
属性支持 Property Expressions,包括默认值和 nestedProperty 表达式。(请注意,"{property.path}"
样式表达式仍然受支持,但不提供 Property 表达式的全部功能。)
The identity
attribute supports Property Expressions including default values and nested
Property Expressions. (Note that "{property.path}"
style expressions are still supported but don’t offer the full functionality of Property Expressions.)
@Scheduled(identity = "${myMethod.identity.expr}")
void myMethod() { }
Delayed Execution
@Scheduled
提供了两种方法来延迟触发器应该开始触发的时刻。
@Scheduled
provides two ways to delay the time a trigger should start firing at.
@Scheduled#delay()
和 @Scheduled#delayUnit()
一同形成初始延迟。
@Scheduled#delay()
and @Scheduled#delayUnit()
form the initial delay together.
@Scheduled(every = "2s", delay = 2, delayUnit = TimeUnit.HOUR) 1
void everyTwoSeconds() { }
1 | The trigger fires for the first time two hours after the application start. |
最终值始终四舍五入到整秒。 |
The final value is always rounded to full second. |
@Scheduled#delayed()
是上面属性的文本替代形式。间隔表达式基于 ISO-8601 持续时间格式 PnDTnHnMn.nS
,并使用 java.time.Duration#parse(CharSequence)
解析值。但是,如果表达式以数字开头并以 d
结尾,将自动添加 P
前缀。如果表达式仅以数字开头,将自动添加 PT
前缀。因此,例如,可以使用 15s
代替 PT15S
,并解析为“15 秒”。
@Scheduled#delayed()
is a text alternative to the properties above.
The period expression is based on the ISO-8601 duration format PnDTnHnMn.nS
and the value is parsed with java.time.Duration#parse(CharSequence)
.
However, if an expression starts with a digit and ends with d
, P
prefix will be added automatically. If the expression only starts with a digit, PT
prefix is added automatically.
So for example, 15s
can be used instead of PT15S
and is parsed as "15 seconds".
@Scheduled(every = "2s", delayed = "2h")
void everyTwoSeconds() { }
如果 |
If |
比 @Scheduled#delay()
的主要优点是该值是可配置的。delay
属性支持 Property Expressions,包括默认值和 nestedProperty 表达式。(请注意,"{property.path}"
样式表达式仍然受支持,但不提供 Property 表达式的全部功能。)
The main advantage over @Scheduled#delay()
is that the value is configurable.
The delay
attribute supports Property Expressions including default values and nested
Property Expressions. (Note that "{property.path}"
style expressions are still supported but don’t offer the full functionality of Property Expressions.)
@Scheduled(every = "2s", delayed = "${myMethod.delay.expr}") 1
void everyTwoSeconds() { }
1 | The config property myMethod.delay.expr is used to set the delay. |
Concurrent Execution
默认情况下,可以同时执行计划的方法。尽管如此,也可以通过 @Scheduled#concurrentExecution()
指定处理并发执行的策略。
By default, a scheduled method can be executed concurrently.
Nevertheless, it is possible to specify the strategy to handle concurrent executions via @Scheduled#concurrentExecution()
.
import static io.quarkus.scheduler.Scheduled.ConcurrentExecution.SKIP;
@Scheduled(every = "1s", concurrentExecution = SKIP) 1
void nonConcurrent() {
// we can be sure that this method is never executed concurrently
}
1 | Concurrent executions are skipped. |
当跳过计划方法的执行时,会触发类型为 |
A CDI event of type |
请注意,仅考虑同一应用程序实例中的执行。此功能不适用于集群。 |
Note that only executions within the same application instance are considered. This feature is not intended to work across the cluster. |
Conditional Execution
你可以通过 @Scheduled#skipExecutionIf()
定义跳过任何计划方法执行的逻辑。指定类必须实现 io.quarkus.scheduler.Scheduled.SkipPredicate
,如果 test()
方法的结果为 true
,则跳过执行。此类必须表示 CDI Bean 或声明公共无参构造函数。对于 CDI,指定类在其 Bean 类型集中必须有且仅有一个 Bean,否则构建失败。此外,Bean 的作用域必须在作业执行期间处于活动状态。如果作用域为 @Dependent
,则 Bean 实例专属特定计划方法,并且应用程序关闭时会被销毁。
You can define the logic to skip any execution of a scheduled method via @Scheduled#skipExecutionIf()
.
The specified class must implement io.quarkus.scheduler.Scheduled.SkipPredicate
and the execution is skipped if the result of the test()
method is true
.
The class must either represent a CDI bean or declare a public no-args constructor.
In case of CDI, there must be exactly one bean that has the specified class in its set of bean types, otherwise the build fails.
Furthermore, the scope of the bean must be active during execution of the job.
If the scope is @Dependent
then the bean instance belongs exclusively to the specific scheduled method and is destroyed when the application is shut down.
class Jobs {
@Scheduled(every = "1s", skipExecutionIf = MyPredicate.class) 1
void everySecond() {
// do something every second...
}
}
@Singleton 2
class MyPredicate implements SkipPredicate {
@Inject
MyService service;
boolean test(ScheduledExecution execution) {
return !service.isStarted(); 3
}
}
1 | A bean instance of MyPredicate.class is used to evaluate whether an execution should be skipped. There must be exactly one bean that has the specified class in its set of bean types, otherwise the build fails. |
2 | The scope of the bean must be active during execution. |
3 | Jobs.everySecond() is skipped until MyService.isStarted() returns true . |
请注意,这等效于以下代码:
Note that this is an equivalent of the following code:
class Jobs {
@Inject
MyService service;
@Scheduled(every = "1s")
void everySecond() {
if (service.isStarted()) {
// do something every second...
}
}
}
其主要思想是将跳过执行的逻辑保持在计划业务方法之外,以便可以轻松地重复使用和重构它。
The main idea is to keep the logic to skip the execution outside the scheduled business methods so that it can be reused and refactored easily.
当跳过计划方法的执行时,会触发类型为 |
A CDI event of type |
要在应用程序启动/关闭时跳过计划执行,可以使用 |
To skip the scheduled executions while the application is starting up/shutting down, you can make use of the |
Non-blocking Methods
默认情况下,在阻塞任务的主执行器上执行计划方法。因此,这样一种被设计为在 Vert.x 事件循环中运行的技术(例如 Hibernate Reactive)不能在方法主体中使用。出于这个原因,返回 java.util.concurrent.CompletionStage<Void>
或 io.smallrye.mutiny.Uni<Void>
,或使用 @io.smallrye.common.annotation.NonBlocking
注释的计划方法相反在 Vert.x 事件循环中执行。
By default, a scheduled method is executed on the main executor for blocking tasks.
As a result, a technology that is designed to run on a Vert.x event loop (such as Hibernate Reactive) cannot be used inside the method body.
For this reason, a scheduled method that returns java.util.concurrent.CompletionStage<Void>
or io.smallrye.mutiny.Uni<Void>
, or is annotated with @io.smallrye.common.annotation.NonBlocking
is executed on the Vert.x event loop instead.
class Jobs {
@Scheduled(every = "1s")
Uni<Void> everySecond() { 1
// ...do something async
}
}
1 | The return type Uni<Void> instructs the scheduler to execute the method on the Vert.x event loop. |
How to use multiple scheduler implementations
在某些情况下,选择用于执行计划方法的计划程序实现可能是有用的。但是,默认情况下,所有计划方法仅使用一个 Scheduler
实现。例如,quarkus-quartz
扩展提供支持群集的实现,但它也从游戏中移除了简单的内存中实现。现在,如果启用了群集,则无法定义计划方法在单个节点上本地执行。不过,如果你将 quarkus.scheduler.use-composite-scheduler
配置属性设置为 true
,那么将改用复合 Scheduler
。这意味着多个计划程序实现将并行运行。此外,可以使用 @Scheduled#executeWith()
选择用于执行计划方法的特定实现。
In some cases, it might be useful to choose a scheduler implementation used to execute a scheduled method.
However, only one Scheduler
implementation is used for all scheduled methods by default.
For example, the quarkus-quartz
extension provides an implementation that supports clustering but it also removes the simple in-memory implementation from the game.
Now, if clustering is enabled then it’s not possible to define a scheduled method that would be executed locally on a single node.
Nevertheless, if you set the quarkus.scheduler.use-composite-scheduler
config property to true
then a composite Scheduler
is used instead.
This means that multiple scheduler implementations are kept running side by side.
Furthermore, it’s possible to chose a specific implementation used to execute a scheduled method using @Scheduled#executeWith()
.
class Jobs {
@Scheduled(cron = "0 15 10 * * ?") 1
void fireAt10AmEveryDay() { }
@Scheduled(every = "1s", executeWith = Scheduled.SIMPLE) 2
void everySecond() { }
}
1 | If the quarkus-quartz extension is present then this method will be executed with the Quartz-specific scheduler. |
2 | If quarkus.scheduler.use-composite-scheduler=true is set then this method will be executed with the simple in-memory implementation provided by the quarkus-scheduler extension. |
Scheduler
Quarkus 提供了类型为 io.quarkus.scheduler.Scheduler
的内置 Bean,可以注入它并使用它来暂停/恢复计划程序和由特定 Scheduled#identity()
标识的单个计划方法。
Quarkus provides a built-in bean of type io.quarkus.scheduler.Scheduler
that can be injected and used to pause/resume the scheduler and individual scheduled methods identified by a specific Scheduled#identity()
.
import io.quarkus.scheduler.Scheduler;
class MyService {
@Inject
Scheduler scheduler;
void ping() {
scheduler.pause(); 1
scheduler.pause("myIdentity"); 2
if (scheduler.isRunning()) {
throw new IllegalStateException("This should never happen!");
}
scheduler.resume("myIdentity"); 3
scheduler.resume(); 4
scheduler.getScheduledJobs(); 5
Trigger jobTrigger = scheduler.getScheduledJob("myIdentity"); 6
if (jobTrigger != null && jobTrigger.isOverdue()){ 7
// the job is late to the party.
}
}
}
1 | Pause all triggers. |
2 | Pause a specific scheduled method by its identity |
3 | Resume a specific scheduled method by its identity |
4 | Resume the scheduler. |
5 | List all jobs in the scheduler. |
6 | Get Trigger metadata for a specific scheduled job by its identity. |
7 | You can configure the grace period for isOverdue() with quarkus.scheduler.overdue-grace-period |
排程器或已排程作业暂停/恢复时,一个 CDI 事件将同步和异步触发。有效负载分别为 |
A CDI event is fired synchronously and asynchronously when the scheduler or a scheduled job is paused/resumed. The payload is |
Programmatic Scheduling
注入的 io.quarkus.scheduler.Scheduler
也可以用于通过编程方式排程作业。
An injected io.quarkus.scheduler.Scheduler
can be also used to schedule a job programmatically.
import io.quarkus.scheduler.Scheduler;
@ApplicationScoped
class MyJobs {
@Inject
Scheduler scheduler;
void addMyJob() { 1
scheduler.newJob("myJob")
.setCron("0/5 * * * * ?")
.setTask(executionContext -> { 2
// do something important every 5 seconds
})
.schedule(); 3
}
void removeMyJob() {
scheduler.unscheduleJob("myJob"); 4
}
}
1 | This is a programmatic alternative to a method annotated with @Scheduled(identity = "myJob", cron = "0/5 * * * * ?") . |
2 | The business logic is defined in a callback. |
3 | The job is scheduled once the JobDefinition#schedule() method is called. |
4 | A job that was added programmatically can be also removed. |
默认情况下,排程器不会启动,除非发现了 |
By default, the scheduler is not started unless a |
如果存在 Quartz extension,并且使用了 DB 存储类型,则无法将任务实例传递给作业定义,而必须使用任务类。Quartz API 也可以用于以编程方式排程作业。 |
If the Quartz extension is present and the DB store type is used then it’s not possible to pass a task instance to the job definition and a task class must be used instead. The Quartz API can be also used to schedule a job programmatically. |
在某些情况下,可能需要更为精细的方法,这就是 Quarkus 也公开可作为 CDI bean 注入的 java.util.concurrent.ScheduledExecutorService
和 java.util.concurrent.ExecutorService
的原因。但是,这些执行器由其他 Quarkus 扩展使用,因此应谨慎使用。此外,用户绝不允许手动关闭这些执行器。
In certain cases, a more fine-grained approach might be needed which is why Quarkus also exposes java.util.concurrent.ScheduledExecutorService
and java.util.concurrent.ExecutorService
that can be injected as CDI beans.
However, these executors are used by other Quarkus extensions and therefore should be approached with caution. Furthermore, users are never allowed to shut these executors down manually.
class JobScheduler {
@Inject
ScheduledExecutorService executor;
void everySecondWithDelay() {
Runnable myRunnable = createMyRunnable();
executor.scheduleAtFixedRate(myRunnable, 3, 1, TimeUnit.SECONDS);
}
}
Scheduled Methods and Testing
在运行测试时,通常希望禁用调度程序。可以通过运行时配置属性 quarkus.scheduler.enabled
禁用调度程序。如果设置为 false
,则即使应用程序包含已排程方法也不会启动调度程序。您甚至可以为特定 Test Profiles 禁用调度程序。
It is often desirable to disable the scheduler when running the tests.
The scheduler can be disabled through the runtime config property quarkus.scheduler.enabled
.
If set to false
the scheduler is not started even though the application contains scheduled methods.
You can even disable the scheduler for particular Test Profiles.
Metrics
如果将 quarkus.scheduler.metrics.enabled
设置为 true
并且存在度量扩展,则会开箱即用地发布一些基本指标。
Some basic metrics are published out of the box if quarkus.scheduler.metrics.enabled
is set to true
and a metrics extension is present.
如果存在 Micrometer extension,则会自动向所有 @Scheduled
方法添加一个 @io.micrometer.core.annotation.Timed
拦截器绑定(除非它已经存在),并且会注册一个名为 scheduled.methods
的 io.micrometer.core.instrument.Timer
和一个名为 scheduled.methods.running
的 io.micrometer.core.instrument.LongTaskTimer
。声明类和 @Scheduled
方法的完全限定名称用作标签。
If the Micrometer extension is present, then a @io.micrometer.core.annotation.Timed
interceptor binding is added to all @Scheduled
methods automatically (unless it’s already present) and a io.micrometer.core.instrument.Timer
with name scheduled.methods
and a io.micrometer.core.instrument.LongTaskTimer
with name scheduled.methods.running
are registered. The fully qualified name of the declaring class and the name of a @Scheduled
method are used as tags.
如果存在 SmallRye Metrics extension,则会自动向所有 @Scheduled
方法添加一个 @org.eclipse.microprofile.metrics.annotation.Timed
拦截器绑定(除非它已经存在),并且会为每个 @Scheduled
方法创建一个 org.eclipse.microprofile.metrics.Timer
。该名称由声明类的完全限定名称和 @Scheduled
方法的名称组成。计时器有一个标签 scheduled=true
。
If the SmallRye Metrics extension is present, then a @org.eclipse.microprofile.metrics.annotation.Timed
interceptor binding is added to all @Scheduled
methods automatically (unless it’s already present) and a org.eclipse.microprofile.metrics.Timer
is created for each @Scheduled
method. The name consists of the fully qualified name of the declaring class and the name of a @Scheduled
method. The timer has a tag scheduled=true
.
OpenTelemetry Tracing
如果将 quarkus.scheduler.tracing.enabled
设置为 true
,并且存在 OpenTelemetry extension,则使用 @Scheduled
注解定义或以编程方式排程的每个作业执行都会自动创建一个以作业的 Identity 命名的范围。
If quarkus.scheduler.tracing.enabled
is set to true
and the OpenTelemetry extension is present then every job execution, either defined with the @Scheduled
annotation or scheduled programmatically, automatically creates a span named after the job’s Identity.
Run @Scheduled methods on virtual threads
用 @Scheduled
注释的方法也可以用 @RunOnVirtualThread
注释。在这种情况下,方法在虚拟线程上调用。
Methods annotated with @Scheduled
can also be annotated with @RunOnVirtualThread
.
In this case, the method is invoked on a virtual thread.
该方法必须返回 void
,并且您的 Java 运行时必须为虚拟线程提供支持。阅读 the virtual thread guide 了解更多详情。
The method must return void
and your Java runtime must provide support for virtual threads.
Read the virtual thread guide for more details.