2015-10-15 71 views
2

根据文档,“执行任务时调用Task#call()”。 考虑下面的程序:执行任务前调用的任务#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状态之前执行了几秒钟。如果我想给用户一些关于状态的反馈,那么什么也没有发生,直到任务已经执行了几秒钟?

回答

3

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

TLDR;版本号:不是。只是在您收到通知它已安排之前调用它。


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

所以,当你在你的线程中调用start(),会发生以下情况幕后:

  1. 一个新的线程启动
  2. 在该线程,在Task内部call()方法被调用。该方法:
  3. 时刻表可运行对FX应用程序线程执行,改变任务的statePropertySCHEDULED
  4. 时刻表可运行对FX应用程序线程执行,改变任务的statePropertyRUNNING
  5. 调用你的call方法

当FX应用程序线程接收改变任务的状态从READYSCHEDULED可运行的,后来从SCHEDULEDRUNNING,它会影响这些更改并通知任何听众。由于这与call方法中的代码位于不同的线程,因此您的call方法中的代码与您的stateProperty听众中的代码之间没有“发生之前”关系。换句话说,不能保证首先会发生什么。特别是,如果FX应用程序线程已经忙于做某事(呈现UI,处理用户输入,处理其他Runnable s传递给Platform.runLater(...)等),它将在对任务stateProperty进行更改之前完成这些操作。

什么可以保证的是,改变SCHEDULEDRUNNING将在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应用程序线程)上,因为状态变化为SCHEDULEDRUNNING,并且由于在状态变化之后显示“任务已启动”消息被安排,在看到“任务已启动”消息之前,您可以保证看到SCHEDULEDRUNNING的转换。 (就类比而言,这与我请助理发送电子邮件一样,然后在我知道她已发送邮件之前不会开始任何工作。)

另外请注意,如果您更换原有的呼叫

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 

这最后一个版本与我的比喻类似,要求我的助理为我发布推文。