2016-12-04 69 views
0

我正在學習JavaFX,並且遇到了運行任務的絆腳石。我使用NetBeans調試器跟蹤了任務的進度,發現ConfigModelSansUpdateTask明顯運行。JavaFX ExecutorService失去任務結果

當應用程序獲取到configModelWorkerSansUpdates.setOnSucceeded,不知何故這個結果(存儲在configModel)變爲null,投擲NullPointerException

在閱讀了關於NullPointerException的文章後,我不明白爲什麼我的任務的結果正在被取消。沒有什麼明顯的是,這樣做,因爲我清楚地得到一個結果就變成了空當我來到使用它:

NetBeans

:當任務即將返回結果的

快照當我來到使用結果:

Null result

幫助!

SWMUApp.java

/* 
* To change this license header, choose License Headers in Project Properties. 
* To change this template file, choose Tools | Templates 
* and open the template in the editor. 
*/ 
package swmuapp; 

import javafx.application.Application; 
import javafx.fxml.FXMLLoader; 
import javafx.scene.Parent; 
import javafx.scene.Scene; 
import javafx.stage.Stage; 
import jaxb.TaskModel; 

/** 
* 
* @author Shaun Connelly-Flynn 
*/ 

public class SWMUApp extends Application { 

public SWMUApp() { 
} 

@Override 
public void start(Stage stage) throws Exception { 
    FXMLLoader centralSeceneLoader = new FXMLLoader(getClass().getResource("forms/CentralScene.fxml")); 
    Parent centralRoot = (Parent) centralSeceneLoader.load(); 

    Scene scene = new Scene(centralRoot); 

    stage.setScene(scene); 
    stage.show(); 
} 

/** 
* @param args the command line arguments 
*/ 
public static void main(String[] args) { 
    launch(args); 
} 

}

CentralSceneController.java

package swmuapp.controllers; 

import java.net.URL; 
import java.util.ResourceBundle; 
import java.util.concurrent.ExecutionException; 
import java.util.concurrent.Future; 
import java.util.logging.Level; 
import java.util.logging.Logger; 
import javafx.concurrent.WorkerStateEvent; 
import javafx.fxml.FXML; 
import javafx.fxml.Initializable; 
import javafx.scene.control.Tab; 
import jaxb.TaskModel; 
import swmuapp.SWMUApp; 
import swmuapp.init.ConfigModelWorkerSansUpdates; 
import swmuapp.init.LocationModelWorker; 
import swmuapp.models.ConfigModel; 
import swmuapp.models.LocationModel; 

/** 
* FXML Controller class 
* 
* @author Shaun Connelly-Flynn 
*/ 

public class CentralSceneController implements Initializable { 

    @FXML 
    private Tab sessionTab; 
    @FXML 
    private Tab tractionTab; 
    @FXML 
    private Tab locationTab; 
    @FXML 
    private Tab swmuTab; 
    @FXML 
    private Tab exportTab; 

    private final TaskModel taskModel; 

    // Models 
    private ConfigModel configModel; 
    private LocationModel locationModel; 

    // Child Controllers 
    @FXML 
    private ImportPaneController importPaneController; 

    @FXML 
    private SWMUPaneController swmuPaneController; 

    @FXML 
    private SessionPaneController sessionPaneController; 

    public CentralSceneController() { 
     this.taskModel = new TaskModel(); 
    } 

    /** 
    * Initialises the controller class. 
    * 
    * @param url 
    * @param rb 
    */ 
    @Override 
    public void initialize(URL url, ResourceBundle rb) { 
     // Give a reference of this controller to the child controllers 
     importPaneController.setParentController(this); 

     // Now subimt the configModel task 
     ConfigModelWorkerSansUpdates configModelWorkerSansUpdates = new ConfigModelWorkerSansUpdates(taskModel); 

     configModelWorkerSansUpdates.setOnSucceeded((WorkerStateEvent event) -> { 
      System.out.println(event.getEventType().toString()); 

      // Get the location model 
      Future<LocationModel> locationFuture = taskModel.submitTask(new LocationModelWorker(configModel, taskModel)); 

      try { 
       locationModel = locationFuture.get(); 
      } catch (InterruptedException | ExecutionException ex) { 
       Logger.getLogger(SWMUApp.class.getName()).log(Level.SEVERE, null, ex); 
      } 

      // Notify the controllers that we're ready 
      sessionPaneController.setConfigModel(configModel); 
      sessionPaneController.setLocationModel(locationModel); 
     }); 

     configModelWorkerSansUpdates.setOnFailed(p -> System.out.println(p.toString())); 
     configModelWorkerSansUpdates.setOnCancelled(p -> System.out.println(p.toString())); 

     // Fetch the config model 
     Future<ConfigModel> configFuture = taskModel.submitTask(configModelWorkerSansUpdates); 

     try { 
      configModel = configFuture.get(); 
     } catch (InterruptedException | ExecutionException ex) { 
      Logger.getLogger(SWMUApp.class.getName()).log(Level.SEVERE, null, ex); 
     } 
    } 
} 

TaskModel.java

/* 
* To change this license header, choose License Headers in Project Properties. 
* To change this template file, choose Tools | Templates 
* and open the template in the editor. 
*/ 
package jaxb; 

import java.util.ArrayList; 
import java.util.List; 
import java.util.concurrent.Callable; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import java.util.concurrent.Future; 
import javafx.concurrent.Task; 

/** 
* 
* @author Shaun Connelly-Flynn 
*/ 
public class TaskModel { 
    private final ExecutorService service; 

    public TaskModel() { 
     this.service = Executors.newCachedThreadPool(); 
    } 

    public Future submitTask(Callable task) { 
     return service.submit(task); 
    } 

    public Future submitTask(Task task) { 
     return service.submit(task); 
    } 

    public List<Future> submitTasks(List<Callable> tasks) { 
     List<Future> futureList = new ArrayList<>(); 

     for(Callable task : tasks) { 
      futureList.add(submitTask(task)); 
     } 

     return futureList; 
    } 
} 

ConfigModelWorkerSansUpdates.java

/* 
* To change this license header, choose License Headers in Project Properties. 
* To change this template file, choose Tools | Templates 
* and open the template in the editor. 
*/ 
package swmuapp.init; 

import java.util.concurrent.ExecutionException; 
import java.util.concurrent.Future; 
import javafx.concurrent.Task; 
import jaxb.Binder; 
import jaxb.TaskModel; 
import jaxb.bundles.QueryBundle; 
import jaxb.bundles.ResultsBundle; 
import jaxb.query.XQuery; 
import swmuapp.config.jaxb.JaxbConfig; 
import swmuapp.config.jaxb.JaxbDatabases; 
import swmuapp.models.ConfigModel; 

/** 
* 
* @author Shaun Connelly-Flynn 
*/ 
public class ConfigModelWorkerSansUpdates extends Task<ConfigModel> { 

    private final TaskModel taskModel; 

    public ConfigModelWorkerSansUpdates(TaskModel taskModel) { 
     this.taskModel = taskModel; 
    } 

    @Override 
    protected ConfigModel call() throws Exception {   
     // Get the list of active databases 
     QueryBundle databaseBundle = new QueryBundle(
       new XQuery("SWMUDB", "<databases>{databases/database}</databases>"), 
       JaxbDatabases.class, 
       "src/swmuapp/schema/DatabaseSchema.xsd"); 

     // Now read the configuration 
     QueryBundle configBundle = new QueryBundle(
       new XQuery("SWMUDB", "configs/config"), 
       JaxbConfig.class, 
       "src/swmuapp/schema/ConfigSchema.xsd"); 

     // Submit the tasks 
     Future<ResultsBundle> dbTask = taskModel.submitTask(new Binder(databaseBundle, taskModel)); 
     Future<ResultsBundle> configTask = taskModel.submitTask(new Binder(configBundle, taskModel)); 

     // Create empty objects in case of failure! 
     JaxbDatabases dbResult = new JaxbDatabases(); 
     JaxbConfig configResult = new JaxbConfig(); 

     try { 
      // Now fetch the list of databases 
      dbResult = (JaxbDatabases) dbTask.get().getResult(); 

      // Fetch the configuration 
      configResult = (JaxbConfig) configTask.get().getResult(); 
     } catch (ExecutionException ex) { 
      setException(ex); 
      throw ex; 
     } 

     // Now put it all together 
     ConfigModel configModel = new ConfigModel(dbResult); 

     // And give it back! 
     return configModel; 
    } 
} 

SessionPaneController.java

package swmuapp.controllers; 

import java.net.URL; 
import java.util.ResourceBundle; 
import javafx.fxml.FXML; 
import javafx.fxml.Initializable; 
import javafx.scene.control.ListView; 
import javafx.scene.control.TableView; 
import swmuapp.models.ConfigModel; 
import swmuapp.models.LocationModel; 

/** 
* FXML Controller class 
* 
* @author Shaun Connelly-Flynn 
*/ 
public class SessionPaneController implements Initializable { 

    @FXML 
    private ListView<String> dbListView; 
    @FXML 
    private TableView<?> detailedTableView; 

    private CentralSceneController controller; 

    private ConfigModel configModel; 
    private LocationModel locationModel; 

    /** 
    * Initializes the controller class. 
    */ 
    @Override 
    public void initialize(URL url, ResourceBundle rb) { 

    } 

    public void setConfigModel(ConfigModel configModel) { 
     if (this.configModel == null) { 
      this.configModel = configModel; 
     } else { 
      throw new IllegalArgumentException("SessionPaneController::setConfigModel -- config model already set!"); 
     } 

     dbListView.setItems(configModel.getDatabases()); 
    } 

    public void setLocationModel(LocationModel locationModel) { 
     this.locationModel = locationModel; 
    } 

    protected void setParentController(CentralSceneController controller) { 
     this.controller = controller; 
    } 
} 

堆棧:

java.util.concurrent.ExecutionException: java.lang.NullPointerException 
    at java.util.concurrent.FutureTask.report(FutureTask.java:122) 
    at java.util.concurrent.FutureTask.get(FutureTask.java:192) 
    at swmuapp.controllers.CentralSceneController.lambda$initialize$0(CentralSceneController.java:84) 
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86) 
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238) 
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191) 
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58) 
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114) 
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74) 
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54) 
    at javafx.event.Event.fireEvent(Event.java:198) 
    at javafx.concurrent.EventHelper.fireEvent(EventHelper.java:219) 
    at javafx.concurrent.Task.fireEvent(Task.java:1356) 
    at javafx.concurrent.Task.setState(Task.java:723) 
    at javafx.concurrent.Task$TaskCallable.lambda$call$500(Task.java:1434) 
    at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295) 
    at java.security.AccessController.doPrivileged(Native Method) 
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294) 
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95) 
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method) 
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191) 
    at java.lang.Thread.run(Thread.java:745) 
Caused by: java.lang.NullPointerException 
    at swmuapp.init.LocationModelWorker.call(LocationModelWorker.java:35) 
    at swmuapp.init.LocationModelWorker.call(LocationModelWorker.java:22) 
    at java.util.concurrent.FutureTask.run(FutureTask.java:266) 
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) 
    ... 1 more 

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException 
    at swmuapp.controllers.SessionPaneController.setConfigModel(SessionPaneController.java:49) 
    at swmuapp.controllers.CentralSceneController.lambda$initialize$0(CentralSceneController.java:90) 
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86) 
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238) 
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191) 
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58) 
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114) 
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74) 
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54) 
    at javafx.event.Event.fireEvent(Event.java:198) 
    at javafx.concurrent.EventHelper.fireEvent(EventHelper.java:219) 
    at javafx.concurrent.Task.fireEvent(Task.java:1356) 
    at javafx.concurrent.Task.setState(Task.java:723) 
    at javafx.concurrent.Task$TaskCallable.lambda$call$500(Task.java:1434) 
    at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295) 
    at java.security.AccessController.doPrivileged(Native Method) 
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294) 
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95) 
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method) 
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191) 
    at java.lang.Thread.run(Thread.java:745) 
+2

可能重複[什麼是NullPointerException,以及如何解決它?](http://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-doi-i-fix -it) –

+0

通過使用調試器執行代碼後,我並不聰明。 – swshaun

回答

0

我從其他帖子中找到了更多信息! TaskRunnableCallable

一個子類,由於在這個帖子中指出:

FX Task, returning a value always returns null

的Javadoc表明:

未來的get方法在成功完成時將會返回null。

請參閱here爲Javadoc上述摘錄是從。

我做了一個錯誤的假設,TaskCallable的實現,但事實上並非如此。

因此Future.get()將在成功完成時返回null,因爲Runnable沒有返回結果。

1

我懷疑的調度決策的某種組合導致前Future.get返回要執行的onSucceeded處理程序,因此CentralSceneController.configModel尚未被分配。

在從同一個線程發佈任務後直接使用Future.get()是一個禁止行爲,因爲它強制當前線程等待任務完成,並可能使用這段時間直接運行問題。如果當前線程不應該被阻塞,就像javafx應用程序線程那樣更糟糕。

修復這個應該是很容易:

只需檢索來自onSucceeded句柄值,因爲此處理任務的應用程序線程上完成後引發反正和使用這種方法來檢索值不會阻止該應用程序線。

以下代碼假定LocationModelWorker延伸Task<LocationModel>

LocationModelWorker重寫採取Future<ConfigModel>作爲constuctor參數代替ConfigModel參數。確保get在此Future未被調用,直到call方法開始(例如,不從構造函數調用它)。

configModelWorkerSansUpdates.setOnSucceeded((WorkerStateEvent event) -> { 
    sessionPaneController.setConfigModel(configModelWorkerSansUpdates.getValue()); 
}); 

... 

Future<ConfigModel> configFuture = taskModel.submitTask(configModelWorkerSansUpdates); 

LocationModelWorker locationModelWorker = new LocationModelWorker(configFuture, taskModel); 
locationModelWorker.setOnSucceeded(evt -> { 
    sessionPaneController.setLocationModel(locationModelWorker.getValue()); 
}); 

... 

taskModel.submitTask(locationModelWorker); 

您可能需要調整這一點的情況下,你只需要調用setLocationModelsetConfigModel如果兩個任務成功完成。

+0

謝謝你的迴應。我試圖從'onSucceeded'處理程序中檢索結果,但我仍然在處理程序中使用'configModel = configFuture.get();'看到同樣的問題。我是否正在做一些嚴重錯誤的事情,比如在錯誤的地方初始化我的模型? – swshaun

+0

另外...我注意到'configFuture.outcome'的內容也是空的 – swshaun

+0

明白了!謝謝你的作品。我已經接受了你的回答 – swshaun