2016-08-18 2 views
1

À peu près, j'essaie d'écrire un programme simple qui permet à l'utilisateur de choisir un fichier. Malheureusement, JFileChooser par l'intermédiaire de Swing est un peu démodé, ainsi j'essaye d'employer JavaFX FileChooser pour ceci. Le but est d'exécuter FileGetter en tant que thread, de transférer les données du fichier à la classe Main et de continuer à partir de là.Boolean wont Mettre à jour à partir de Object.getBoolean();

Classe principale:

package application; 
import java.io.File; 
import javafx.application.Application; 
import javafx.stage.Stage; 
import javafx.scene.Scene; 
import javafx.scene.layout.BorderPane; 


public class Main { 

    public static void main(String[] args) { 
     Thread t1 = new Thread(new FileGetter()); 
     FileGetter fg = new FileGetter(); 
     t1.start(); 
     boolean isReady = false; 
     while(isReady == false){ 
      isReady = FileGetter.getIsReady();  
     } 
     File file = FileGetter.getFile(); 

     System.out.println(file.getAbsolutePath()); 
     ... 

    } 
} 

FileGetter Classe:

package application; 

import java.io.File; 
import javafx.application.Application; 
import javafx.application.Platform; 
import javafx.stage.FileChooser; 
import javafx.stage.Stage; 
import javafx.scene.Scene; 
import javafx.scene.layout.BorderPane; 


public class FileGetter extends Application implements Runnable { 

    static File file; 
    static boolean isReady = false; 


    @Override 
    public void start(Stage primaryStage) { 
     try { 

      FileChooser fc = new FileChooser(); 
      while(file == null){ 
      file = fc.showOpenDialog(primaryStage); 
      } 
      isReady = true; 
      Platform.exit(); 
     } catch(Exception e) { 
      e.printStackTrace(); 
     } 
    } 
    @Override 
    public void run() { 
     launch(); 
    } 

    public static boolean getIsReady(){ 
     return isReady; 
    } 

    public static File getFile(){ 
     return file; 
    } 

} 

Le problème est que la valeur de isReady dans la boucle while ne met pas à jour true lorsque l'utilisateur a choisi un fichier (la raison Je l'ai fait pour empêcher le code dans Main de continuer avec un ensemble de fichiers à null).

Toute aide, suggestion alternative, ou explication quant à pourquoi cela se produit est très appréciée!

+4

Faites votre 'isReady' dans FileGetter' volatile'. – Codebender

+0

@Codebender Cela a fonctionné! Merci! Juste curieux comme une note de côté, pour une raison quelconque cela a fonctionné quand j'ai inclus un System.out.println (isReady); dans la boucle while. Quelqu'un connaît la raison de cela? –

+0

La boucle while de FileGetter # start permet de s'assurer que l'utilisateur choisit un fichier, tandis que la boucle while de la classe principale permet de s'assurer que le code ne se poursuit pas avant qu'un fichier ne soit choisi. Mais oui, je pourrais utiliser la même déclaration conditionnelle dans les deux, mais je crois que c'est le même concept. –

Répondre

2

La meilleure façon de mettre en œuvre cette

Au lieu d'essayer de conduire le cheval avec le chariot, pourquoi ne pas simplement suivre le cycle de vie standard JavaFX? En d'autres termes, faites votre classe Main une sous-classe de Application, obtenez le fichier dans la méthode start(), puis passez (dans un fil d'arrière-plan) avec le reste de l'application?une fois que vous avez décidé:

public class Main extends Application { 

    @Override 
    public void init() { 
     // make sure we don't exit when file chooser is closed... 
     Platform.setImplicitExit(false); 
    } 

    @Override 
    public void start(Stage primaryStage) { 
     File file = null ; 
     FileChooser fc = new FileChooser(); 
     while(file == null){ 
      file = fc.showOpenDialog(primaryStage); 
     } 
     final File theFile = file ; 
     new Thread(() -> runApplication(theFile)).start(); 
    } 

    private void runApplication(File file) { 
     // run your application here... 
    } 

} 

Quel est le problème avec votre code

Si vous voulez vraiment la classe Main être séparée de la classe JavaFX Application (qui ne fait pas vraiment de sens pour utiliser un JavaFX FileChooser, vous avez décidé d'écrire une application JavaFX, donc la classe de démarrage devrait être une sous-classe de Application), alors ça devient un peu compliqué. Il y a plusieurs problèmes avec votre code tel qu'il est, dont certains sont abordés dans d'autres réponses. Le problème principal, comme indiqué dans la réponse de Fabian, est que vous faites référence à FileGetter.isReady à partir de plusieurs threads sans assurer la vivacité. C'est exactement le problème abordé dans le Effective Java de Josh Bloch (article 66 de la 2e édition).

Un autre problème avec votre code est que vous ne serez pas en mesure d'utiliser le FileGetter plus d'une fois (vous ne pouvez pas appeler launch() plus d'une fois), ce qui peut ne pas être un problème dans votre code maintenant, mais presque certainement sera à un moment donné avec cette application que le développement progresse. Le problème est que vous avez mélangé deux problèmes: démarrage de la boîte à outils FX et récupération d'un fichier à partir d'un FileChooser. La première chose ne doit être faite qu'une seule fois; la seconde devrait être écrite pour être réutilisable.

Et enfin votre boucle

while(isReady == false){ 
    isReady = FileGetter.getIsReady();  
} 

est très mauvaise pratique: il vérifie le drapeau isReady aussi vite qu'il le peut. Dans certaines circonstances (assez inhabituelles), il pourrait même empêcher le thread d'application FX d'avoir des ressources à exécuter. Cela devrait juste bloquer jusqu'à ce que le fichier soit prêt.


Comment fixer sans Main un JavaFX Application

Alors, encore une fois que si vous avez vraiment besoin pressant de le faire, je voudrais d'abord créer une classe qui a seulement la responsabilité de départ la boîte à outils FX. Quelque chose comme:

import java.util.concurrent.CountDownLatch; 
import java.util.concurrent.atomic.AtomicBoolean; 

import javafx.application.Application; 
import javafx.application.Platform; 
import javafx.stage.Stage; 

public class FXStarter extends Application { 

    private static final AtomicBoolean startRequested = new AtomicBoolean(false); 
    private static final CountDownLatch latch = new CountDownLatch(1); 

    @Override 
    public void init() { 
     Platform.setImplicitExit(false); 
    } 

    @Override 
    public void start(Stage primaryStage) { 
     latch.countDown(); 
    } 

    /** Starts the FX toolkit, if not already started via this method, 
    ** and blocks execution until it is running. 
    **/ 
    public static void startFXIfNeeded() throws InterruptedException { 
     if (! startRequested.getAndSet(true)) { 
      new Thread(Application::launch).start(); 
     } 
     latch.await(); 
    } 
} 

Créez maintenant une classe qui obtient un fichier pour vous. Cela devrait assurer l'exécution de la boîte à outils FX, en utilisant la classe précédente. Cette implémentation vous permet d'appeler getFile() de tout fil:

import java.io.File; 
import java.util.concurrent.ExecutionException; 
import java.util.concurrent.FutureTask; 

import javafx.application.Platform; 
import javafx.stage.FileChooser; 


public class FileGetter { 

    /** 
    ** Retrieves a file from a JavaFX File chooser. This method can 
    ** be called from any thread, and will block until the user chooses 
    ** a file. 
    **/ 

    public File getFile() throws InterruptedException { 

     FXStarter.startFXIfNeeded() ; 

     if (Platform.isFxApplicationThread()) { 
      return doGetFile(); 
     } else { 
      FutureTask<File> task = new FutureTask<File>(this::doGetFile); 
      Platform.runLater(task); 
      try { 
       return task.get(); 
      } catch (ExecutionException exc) { 
       throw new RuntimeException(exc); 
      } 
     } 
    } 

    private File doGetFile() { 
     File file = null ; 
     FileChooser chooser = new FileChooser() ; 

     while (file == null) { 
      file = chooser.showOpenDialog(null) ; 
     } 

     return file ; 
    } 
} 

et enfin votre Main est juste

import java.io.File; 

public class Main { 

    public static void main(String[] args) throws InterruptedException { 
     File file = new FileGetter().getFile(); 
     // proceed... 
    } 
} 

Encore une fois, cela est assez complexe; Je ne vois aucune raison de ne pas simplement utiliser le cycle de vie standard de l'application FX pour cela, comme dans le tout premier bloc de code dans la réponse.

+0

Quels avantages utiliserait votre code qui a toujours la classe 'main' sur mon code (si je le mets à jour pour stocker les données de fichier dans ma classe' main' et mettre à jour les types booléen et File à volatile) (ne pas essayer de discréditer votre code ou quoi que ce soit, juste curieux sur les avantages)? –

+0

Vous ne savez pas ce que vous entendez par "mettre à jour pour stocker les données de fichier dans ma classe' Main' Comment voulez-vous démarrer le toolkit FX? –

+0

Il suffit de créer une variable de fichier dans ma classe 'main' et de stocker le choix là (je pense que vous faisiez référence précédemment que je ne pourrais pas continuer à référencer le fichier par 'FileGetter.getFile()' quand le thread s'est arrêté, donc je le stocke dans une variable de fichier?). –

2

Dans ce code

while(isReady == false){ 
     isReady = FileGetter.getIsReady();  
} 

il n'y a rien qui va changer l'état de l » isReady à trueFileGetter

+0

Attendez-vous à quand 'file' devient non-nul dans' FileGetter # start'? Je pourrais juste manquer quelque chose. –

+0

Ouais, j'ai essayé, mais ça ne marche pas pour une raison quelconque ... Et isReady devrait mettre à vrai quand l'utilisateur choisit un fichier du FileChooser. –

+1

C'est aussi une terrible attente. – chrylis

3

Le modèle de mémoire java ne nécessite pas de valeurs variables être les mêmes dans les différents threads sauf dans des conditions spécifiques. Ce qui se passe ici, c'est que le thread FileGetter met à jour la valeur dans la propre mémoire qui n'est accessible qu'à partir de ce thread, mais votre thread principal ne voit pas la valeur mise à jour, puisqu'il ne voit que la version de la variable stocké dans sa propre mémoire qui est différent de celui du thread FileGetter. Chacun des threads possède sa propre copie du champ en mémoire, ce qui est parfaitement conforme à la spécification Java.

Pour résoudre ce problème, vous pouvez simplement ajouter le modificateur volatile à isReady:

static volatile boolean isReady = false; 

ce qui fait que la valeur mise à jour sera visible à partir de votre thread principal.

En outre, je recommande de réduire le nombre de FileGetter instances que vous créez. Dans votre code 3 instances sont créées, mais seulement 1 est utilisé.

Thread t1 = new Thread(() -> Application.launch(FileGetter.class)); 
t1.start(); 
...