在执行任务之前调用Task#call()方法

时间:2015-10-15 15:34:38

标签: javafx concurrency task scheduled-tasks

根据文档,Task#call()是“执行任务时调用的”。 请考虑以下程序:

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.stage.Stage;

public class TestTask extends Application {

    Long start;

    public void start(Stage stage) {

        start = System.currentTimeMillis();

        new Thread(new Taskus()).start(); 
    }

    public static void main(String[] args) {
        launch();
    }

    class Taskus extends Task<Void> {

        public Taskus() {
            stateProperty().addListener((obs, oldValue, newValue) -> {
                try {
                    System.out.println(newValue + " at " + (System.currentTimeMillis()-start));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }

        public Void call() throws InterruptedException {

            for (int i = 0; i < 10000; i++) {
                // Could be a lot longer.
            }
            System.out.println("Some code already executed." + " at " + (System.currentTimeMillis()-start));

            Thread.sleep(3000);

            return null;
        }
    }
}

执行此程序会给我以下输出:

Some code already executed. after 5 milliseconds
SCHEDULED after 5 milliseconds
RUNNING after 7 milliseconds
SUCCEEDED after 3005 milliseconds

为什么在任务被安排之前调用call()方法?这对我来说毫无意义。在我第一次看到问题的任务中,我的任务在任务进入SCHEDULED状态前几秒执行。如果我想向用户提供有关状态的一些反馈,并且在任务已执行几秒钟之前没有任何反应,该怎么办?

1 个答案:

答案 0 :(得分:4)

  

为什么在任务被安排之前调用call()方法?

<强> TLDR;版本:不是。只有在您收到通知已被安排的时间之前,才会调用它。

你有两个线程在运行,基本上是独立的:你明确创建的线程和FX应用程序线程。当您启动应用程序线程时,它将在该线程上调用Taskus.call()。但是,通过调用Platform.runLater(...),可以在FX应用程序线程上更改任务的属性。

因此,当您在线程上调用start()时,会在幕后发生以下情况:

  1. 启动新主题
  2. 在该线程上,调用call()中的内部Task方法。那个方法:
  3. 计划在FX应用程序线程上执行runnable,将任务的stateProperty更改为SCHEDULED
  4. 计划在FX应用程序线程上执行runnable,将任务的stateProperty更改为RUNNING
  5. 调用call方法
  6. 当FX应用程序线程收到可将任务状态从READY更改为SCHEDULED以及稍后从SCHEDULED更改为RUNNING的runnable时,它会影响这些更改并通知任何听众。由于这与call方法中的代码位于不同的主题上,因此没有&#34;发生之前&#34; call方法中的代码与stateProperty侦听器中的代码之间的关系。换句话说,不能保证首先会发生什么。特别是,如果FX应用程序线程已经忙于执行某些操作(呈现UI,处理用户输入,处理传递给Runnable的其他Platform.runLater(...)等),它将在进行更改之前完成任务的stateProperty

    您可以保证,SCHEDULEDRUNNING的更改将在您的{{1}之前在FX应用程序主题上进行计划(但不一定已执行)调用方法,并且在执行call更改之前将执行对SCHEDULED的更改。

    这是一个类比。假设我接受客户的请求来编写软件。将我的工作流视为后台线程。假设我有一位管理员助理,他会与我联系。将她的工作流程视为FX Application线程。因此,当我收到客户的请求时,我会告诉我的管理员向客户发送电子邮件并通知他们我收到了请求(RUNNING)。我的行政助理尽职尽责地把这件事放在她的“待办事项”上。名单。不久之后,我告诉我的行政助理给客户发电子邮件告诉他们我已经开始处理他们的项目(SCHEDULED)了,然后她将这些内容添加到她的&#34;待办事项&#34;名单。然后我开始研究这个项目。我在这个项目上做了一些工作,然后进入推特并发布了一条推文(你的RUNNING)&#34;在xxx的项目上工作,这真的很有趣!&#34;。取决于我的助手已经完成的事情&#34;待办事项&#34;列表,在将电子邮件发送给客户之前,推文可能完全可能出现,因此客户完全有可能看到我已开始处理项目,然后才看到电子邮件说工作已安排,即使从我工作流程的视角,一切都以正确的顺序发生。

    这通常是您想要的:status属性旨在用于更新UI,因此它必须在FX Application Thread上运行。由于您在不同的线程上运行任务,因此您可能希望它能够执行此操作:在不同的执行线程中运行。

    在调用方法实际开始执行之后,似乎不太可能观察到对预定状态的更改有大量时间(多于一帧渲染脉冲,通常为1/60秒):如果发生了这种情况可能会阻止FX Application线程在某处阻止它看到这些更改。在您的示例中,时间延迟显然是最小的(小于一毫秒)。

    如果您想在任务开始时执行某些操作,但不关心您执行该操作的线程,请在调用方法的开头执行此操作。 (就上述类比而言,这相当于我向客户发送电子邮件,而不是要求我的助手这样做。)

    如果您在调用方法中确实需要在FX应用程序线程上发生某些用户通知后发生代码,则需要使用以下模式:

    System.out.println("Some code already executed")

    在此示例中,您可以确保看到&#34;任务已启动&#34;在您看到&#34;某些代码已经执行&#34;之前。此外,由于显示&#34;任务已经开始&#34;方法发生在同一个线程(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 ; } } SCHEDULED,并且因为显示&#34;任务已经开始&#34;在状态更改后安排邮件,您可以确保在看到&#34;任务已启动之前看到转换为RUNNINGSCHEDULED&#34;信息。 (就类比而言,这与我要求助理发送电子邮件,然后在我知道她已发送电子邮件之前没有开始任何工作一样。)

    另请注意,如果您将原始呼叫替换为

    RUNNING

    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
    

    这最后一个版本与我的比喻相同,要求我的助手为我发布推文。