为什么在调度任务之前调用call()方法?
TLDR;版本号:不是。只是在您收到通知它已安排之前调用它。
您有两个线程运行,实质上是独立的:您明确创建的线程和FX应用程序线程。当你启动你的应用程序线程时,它将在该线程上调用Taskus.call()
。但是,通过调用Platform.runLater(...)
,可以在FX应用程序线程中对任务属性进行更改。
所以,当你在你的线程中调用start()
,会发生以下情况幕后:
- 一个新的线程启动
- 在该线程,在
Task
内部call()
方法被调用。该方法:
- 时刻表可运行对FX应用程序线程执行,改变任务的
stateProperty
到SCHEDULED
- 时刻表可运行对FX应用程序线程执行,改变任务的
stateProperty
到RUNNING
- 调用你的
call
方法
当FX应用程序线程接收改变任务的状态从READY
到SCHEDULED
可运行的,后来从SCHEDULED
到RUNNING
,它会影响这些更改并通知任何听众。由于这与call
方法中的代码位于不同的线程,因此您的call
方法中的代码与您的stateProperty
听众中的代码之间没有“发生之前”关系。换句话说,不能保证首先会发生什么。特别是,如果FX应用程序线程已经忙于做某事(呈现UI,处理用户输入,处理其他Runnable
s传递给Platform.runLater(...)
等),它将在对任务stateProperty
进行更改之前完成这些操作。
什么可以保证的是,改变SCHEDULED
和RUNNING
将在FX应用程序线程计划(但不一定是执行)您call
方法之前被调用,并以SCHEDULED
变化会前执行执行RUNNING
的更改。
下面是一个比喻。假设我接受客户的请求来编写软件。把我的工作流想象成后台线程。假设我有一位管理员助理为我与顾客进行沟通。把她的工作流想象成FX应用程序线程。因此,当我收到客户的请求时,我会告诉我的管理员助理向客户发送电子邮件并通知他们我收到了请求(SCHEDULED
)。我的管理员助理忠实地将其放在她的“待办事项”清单上。不久之后,我告诉我的管理员助理向客户发送电子邮件,告诉他们我已经开始致力于他们的项目(RUNNING
),并将其添加到她的“待办事项”列表中。然后我开始研究这个项目。我在这个项目上做了一些工作,然后进入Twitter并发布一条推文(你的System.out.println("Some code already executed")
)“为xxx工作,这真的很有趣!”。根据我助手的“待办事项”列表中已有的事情数量,在将电子邮件发送给客户之前,推文很可能会出现,因此客户很可能会看到我已经在看到该项目之前开始工作即使从我的工作流程的角度来看,即使从工作流程的角度来看,所有事情都按照正确的顺序进行。
这通常是您想要的:status属性旨在用于更新UI,因此它必须在FX应用程序线程上运行。既然你在不同的线程上运行你的任务,你可能希望它做到这一点:运行在另一个执行线程中。
在调用方法实际开始执行之后,我认为不可能在预定状态的变化中观察到大量时间(多于一帧渲染脉冲,通常为1/60秒):如果发生这种情况您可能会阻止FX应用程序线程以防止它看到这些更改。在你的例子中,时间延迟显然是最小的(小于一毫秒)。
如果您想在任务开始时执行某些操作,但不关心执行哪个线程,请在调用方法开始时执行此操作。 (就上面的类比而言,这相当于我将电子邮件发送给客户,而不是要求我的助理这样做。)
如果您确实需要在您的调用方法中的代码发生在某个用户通知已经发生的FX应用程序线程,则需要使用以下模式:
public class Taskus extends Task<Void> {
@Override
public Void call() throws Exception {
FutureTask<Void> uiUpdate = new FutureTask<Void>(() -> {
System.out.println("Task has started");
// do some UI update here...
return null ;
});
Platform.runLater(uiUpdate);
// wait for update:
uiUpdate.get();
for (int i = 0; i < 10000; i++) {
// any VM implementation worth using is going
// to ignore this loop, by the way...
}
System.out.println("Some code already executed." + " at " + (System.currentTimeMillis()-start));
Thread.sleep(3000);
return null ;
}
}
在这个例子中,你肯定可以看到“任务已启动”之前看到“已执行的一些代码”。此外,由于显示“任务已启动”方法发生在同一个线程(FX应用程序线程)上,因为状态变化为SCHEDULED
和RUNNING
,并且由于在状态变化之后显示“任务已启动”消息被安排,在看到“任务已启动”消息之前,您可以保证看到SCHEDULED
和RUNNING
的转换。 (就类比而言,这与我请助理发送电子邮件一样,然后在我知道她已发送邮件之前不会开始任何工作。)
另外请注意,如果您更换原有的呼叫
System.out.println("Some code already executed." + " at " + (System.currentTimeMillis()-start));
与
Platform.runLater(() ->
System.out.println("Some code already executed." + " at " + (System.currentTimeMillis()-start)));
,那么你也保证看到的顺序调用你期望:
SCHEDULED after 5 milliseconds
RUNNING after 7 milliseconds
Some code already executed. after 8 milliseconds
SUCCEEDED after 3008 milliseconds
这最后一个版本与我的比喻类似,要求我的助理为我发布推文。