简体   繁体   中英

Why is ExecutorService not returning the results of my Task?

UPDATE:

I have a button on a JavaFx App that should do the login after user input email and password.

<Button fx:id="loginButton" layoutX="157.0" layoutY="254.0" mnemonicParsing="false" onAction="#login" prefHeight="30.0" prefWidth="172.0" text="Login" />

I have an ExecutorService on a JavaFX app. I created a task to search the DB. It executes the query well and gets the UserInfo object, the problem is, the ExecutorService is not passing the results to the main thread. This is the main code that is having a problem of null returned from the executor service:

public class LoginController {
    @FXML
    private Button loginButton;
    @FXML
    private Label afterLoginText;
    @FXML
    private TextField email;
    @FXML
    private PasswordField password;
    @FXML
    private Hyperlink hyperlink;
    @FXML
    private ProgressBar progressBar;
    private Navegador navegador;

    public void login(ActionEvent event) {
        afterLoginText.setText("Login in, please wait...");
        String emailText = email.getText();
        String passwordText = password.getText();
        DAOGeneric<UserInfo> dao = new DAOGeneric<>();
        LoginAtDataBaseTask loginAtDataBaseTask = new LoginAtDataBaseTask(dao, emailText, passwordText);
        progressBar.progressProperty().bind(loginAtDataBaseTask.progressProperty());
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Future future = executorService.submit(loginAtDataBaseTask);
        loginAtDataBaseTask.setOnSucceeded(workerStateEvent -> {
            UserInfo userInfo;
            try {
                userInfo = (UserInfo) future.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            } catch (ExecutionException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
            if(userInfo == null){
                System.out.println("User info is null");
            }
            else{
                try {
                    changeToMainScreen(event, userInfo);
                } catch (IOException e) {
                    e.printStackTrace();
                    throw new RuntimeException(e);
                }
                //Set premium level if user have one
                //Optional - show premium info
            }
        });
        executorService.shutdown();
    }
    public void changeToMainScreen(ActionEvent event, UserInfo userInfo) throws IOException {
        Stage stage = (Stage) ((Node)event.getSource()).getScene().getWindow();
        runMainScreen("/mainScreen.fxml",stage, userInfo);
    }

And here is the code of the LoginAtDataBaseTask Class:

public class LoginAtDataBaseTask extends Task <UserInfo> {

    private static DAOGeneric<UserInfo> dao;

    private static String email;

    private static String password;
    public LoginAtDataBaseTask(DAOGeneric<UserInfo> dao, String email, String password) {
        this.dao = dao;
        this.email = email;
        this.password = password;
    }

    @Override
    protected UserInfo call() {
        return doLogin();
    }

    private UserInfo doLogin(){
        final int maxProgress = 100;
        List<UserInfo> usersList = dao.findByAnyParameter("email", email, UserInfo.class);
        if(usersList.size() == 1){
            updateProgress(99,maxProgress);
            UserInfo user1 = usersList.get(0);
            String passwordDecoded = DecoderMD5.StringToMD5(password);
            if(user1.getUserPassword().equals(passwordDecoded)){
//                afterLoginText.setText("Login Sucess!");
                return user1;
            }
            else{
//                afterLoginText.setText("Wrong Password!");
            }
        }
        else if(usersList.size()>1){
            //More than one user with same email on BD
//            afterLoginText.setText("Error code 1 - report to administrator");
        }
        else if(usersList.size()==0){
//            afterLoginText.setText("This email is not registered! Please register first!");
        }
        else{
            //Erro at DAO Search
//            afterLoginText.setText("Error code 2 - report to administrator");
        }
        return null;
    }
}

I tried casting on many ways and using Future first to receive the submit and then calling get on the future object, but nothing seems to work. I have already readed the java documents of this related classes but i don't really understand why my object keeps null.

UPDATE: I put a setOnSucceeded but the future.get keeps returning null, and the main javafx ui keeps freezing. What am i keeping doing wrong and what can i do to solve it?

Why does get() return null ?

This has to do with the fact that Task is, fundamentally, an implementation of Runnable . It is not an implementation of Callable . Therefore, you are calling #submit(Runnable) which returns a Future<?> 1 , meaning no result is expected. A Runnable cannot return a value. In other words, the call to #get() will always return null in this case.

But you should really be calling #execute(Runnable) when passing a Task to an ExecutorService , anyway. There's no reason to have a Future object representing the status of the Task . This is for at least two reasons:

  1. The call to Future#get() is a blocking call . The whole purpose of Task is to communincate a result specifically back to the JavaFX Application Thread . And you must never block that thread, as doing so will lead to an unresponsive UI.

  2. A Task is a FutureTask 2 , which means it is already a Future . If you really need to wait for a result ( not on the FX thread ), then you can just call #get() on the Task instance.


1. It really should have been defined to return Future<Void>

2. Note that the standard ExecutorService implementation, ThreadPoolExecutor , wraps all submitted Runnable and Callable objects in a FutureTask , at least by default.


How to get the result of a Task

As noted earlier, the purpose of Task (and the other javafx.concurrent classes) is to offload work to a background thread but communicate a result (as well as messages, progress, etc.) back to the JavaFX Application Thread . That thread specifically, not any other thread. But you must not block the FX thread. That means observing the task for completion, not waiting for it to complete. Then when it does complete, you react by doing what needs doing.

But how to get the value from a completed Task ? You query its value property, which will be set to whatever is returned by the #call() method if and when the task succeeds. You can directly observe this property with a listener, if you want. Personally, I prefer using the onSucceeded and onFailed properties. For example:

Task<SomeObject> task = ...;

task.setOnSucceeded(e -> {
    SomeObject result = task.getValue();
    // do something with 'result'
});

task.setOnFailed(e -> {
    task.getException().printStackTrace(); // or log it with a proper logging framework
    // notify user of failure
});

executor.execute(task);

Note properties of Task like message , progress , value and so on are guaranteed to only be set by the FX thread. And the onXXX handlers are also guaranteed to be invoked only by the FX thread.

See Concurrency in JavaFX and javafx.concurrent documentation for more information.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM