Forcer l'arrêt Fichiers Java.copy () tournant sur le thread externe

la réponse ici semblait être une solution valable avant Java 8: comment annuler des fichiers.copier() en Java?

mais maintenant ça ne marche pas, parce que ExtendedCopyOption.INTERRUPTIBLE est privé.


fondamentalement, je dois télécharger un fichier à partir de certains URL et le sauvegarder dans mon système de fichiers local en utilisant Files.copy() . Actuellement, J'utilise un service JavaFX parce que j'ai besoin de montrer les progrès dans un ProgressBar .

cependant, je ne sais pas comment bloquer le fil tournant Files.copy() si l'opération prend trop de temps. Utiliser Thread.stop() n'est pas souhaité. Même Thread.interrupt() échoue.

je veux aussi que l'opération se termine gracieusement si la connexion internet devient indisponible.

pour tester le cas quand aucune connexion internet n'est disponible, je retire mon câble ethernet et je le remets après 3 deuxième. Malheureusement, Files.copy() ne retourne que lorsque j'ai remis le câble ethernet, alors que je voudrais qu'elle échoue immédiatement.

comme je peux le voir, en interne Files.copy() exécute une boucle, qui empêche le fil de sortir.


Tester (téléchargement OBS Studio exe):

/**
 * @author GOXR3PLUS
 *
 */
public class TestDownloader extends Application {

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

    @Override
    public void start(Stage primaryStage) throws Exception {
    // Block From exiting
    Platform.setImplicitExit(false);

    // Try to download the File from URL
    new DownloadService().startDownload(
        "https://github.com/jp9000/obs-studio/releases/download/17.0.2/OBS-Studio-17.0.2-Small-Installer.exe",
        System.getProperty("user.home") + File.separator + "Desktop" + File.separator + "OBS-Studio-17.0.2-Small-Installer.exe");

    }

}

DownloadService :

en utilisant @sillyfly commentaire avec FileChannel et supprimer File.copy semble fonctionner seulement avec l'appel Thread.interrupt() , mais il ne sort pas lorsque l'internet n'est pas disponible..

import java.io.File;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
import java.util.logging.Level;
import java.util.logging.Logger;

import javafx.concurrent.Service;
import javafx.concurrent.Task;

/**
 * JavaFX Service which is Capable of Downloading Files from the Internet to the
 * LocalHost
 * 
 * @author GOXR3PLUS
 *
 */
public class DownloadService extends Service<Boolean> {

    // -----
    private long totalBytes;
    private boolean succeeded = false;
    private volatile boolean stopThread;

    // CopyThread
    private Thread copyThread = null;

    // ----
    private String urlString;
    private String destination;

    /**
     * The logger of the class
     */
    private static final Logger LOGGER = Logger.getLogger(DownloadService.class.getName());

    /**
     * Constructor
     */
    public DownloadService() {
    setOnFailed(f -> System.out.println("Failed with value: " + super.getValue()+" , Copy Thread is Alive? "+copyThread.isAlive()));
    setOnSucceeded(s -> System.out.println("Succeeded with value: " + super.getValue()+" , Copy Thread is Alive? "+copyThread.isAlive()));
    setOnCancelled(c -> System.out.println("Succeeded with value: " + super.getValue()+" , Copy Thread is Alive? "+copyThread.isAlive()));
    }

    /**
     * Start the Download Service
     * 
     * @param urlString
     *            The source File URL
     * @param destination
     *            The destination File
     */
    public void startDownload(String urlString, String destination) {
    if (!super.isRunning()) {
        this.urlString = urlString;
        this.destination = destination;
        totalBytes = 0;
        restart();
    }
    }

    @Override
    protected Task<Boolean> createTask() {
    return new Task<Boolean>() {
        @Override
        protected Boolean call() throws Exception {

        // Succeeded boolean
        succeeded = true;

        // URL and LocalFile
        URL urlFile = new URL(java.net.URLDecoder.decode(urlString, "UTF-8"));
        File destinationFile = new File(destination);

        try {
            // Open the connection and get totalBytes
            URLConnection connection = urlFile.openConnection();
            totalBytes = Long.parseLong(connection.getHeaderField("Content-Length"));





            // --------------------- Copy the File to External Thread-----------
            copyThread = new Thread(() -> {

            // Start File Copy
            try (FileChannel zip = FileChannel.open(destinationFile.toPath(), StandardOpenOption.CREATE,
                StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {

                zip.transferFrom(Channels.newChannel(connection.getInputStream()), 0, Long.MAX_VALUE);


                // Files.copy(dl.openStream(), fl.toPath(),StandardCopyOption.REPLACE_EXISTING)

            } catch (Exception ex) {
                stopThread = true;
                LOGGER.log(Level.WARNING, "DownloadService failed", ex);
            }

            System.out.println("Copy Thread exited...");
            });
            // Set to Daemon
            copyThread.setDaemon(true);
            // Start the Thread
            copyThread.start();
            // -------------------- End of Copy the File to External Thread-------






            // ---------------------------Check the %100 Progress--------------------
            long outPutFileLength;
            long previousLength = 0;
            int failCounter = 0;
            // While Loop
            while ((outPutFileLength = destinationFile.length()) < totalBytes && !stopThread) {

            // Check the previous length
            if (previousLength != outPutFileLength) {
                previousLength = outPutFileLength;
                failCounter = 0;
            } else
                ++failCounter;

            // 2 Seconds passed without response
            if (failCounter == 40 || stopThread)
                break;

            // Update Progress
            super.updateProgress((outPutFileLength * 100) / totalBytes, 100);
            System.out.println("Current Bytes:" + outPutFileLength + " ,|, TotalBytes:" + totalBytes
                + " ,|, Current Progress: " + (outPutFileLength * 100) / totalBytes + " %");

            // Sleep
            try {
                Thread.sleep(50);
            } catch (InterruptedException ex) {
                LOGGER.log(Level.WARNING, "", ex);
            }
            }

            // 2 Seconds passed without response
            if (failCounter == 40)
            succeeded = false;
           // --------------------------End of Check the %100 Progress--------------------

        } catch (Exception ex) {
            succeeded = false;
            // Stop the External Thread which is updating the %100
            // progress
            stopThread = true;
            LOGGER.log(Level.WARNING, "DownloadService failed", ex);
        }







        //----------------------Finally------------------------------

        System.out.println("Trying to interrupt[shoot with an assault rifle] the copy Thread");

        // ---FORCE STOP COPY FILES
        if (copyThread != null && copyThread.isAlive()) {
            copyThread.interrupt();
            System.out.println("Done an interrupt to the copy Thread");

            // Run a Looping checking if the copyThread has stopped...
            while (copyThread.isAlive()) {
            System.out.println("Copy Thread is still Alive,refusing to die.");
            Thread.sleep(50);
            }
        }

        System.out.println("Download Service exited:[Value=" + succeeded + "] Copy Thread is Alive? "
            + (copyThread == null ? "" : copyThread.isAlive()));

        //---------------------- End of Finally------------------------------




        return succeeded;
        }

    };
    }

}

questions intéressantes:

1-> Qu'est java.lang.Fil.interrompre () do?

23
demandé sur Community 2017-02-05 13:54:11

3 réponses

je vous encourage fortement à utiliser un FileChannel . Il a la méthode transferFrom() qui retourne immédiatement quand le fil courant il est interrompu. (Le Javadoc dit ici qu'il devrait soulever un ClosedByInterruptException , mais il ne le fait pas.)

try (FileChannel channel = FileChannel.open(Paths.get(...), StandardOpenOption.CREATE,
                                            StandardOpenOption.WRITE)) {
    channel.transferFrom(Channels.newChannel(new URL(...).openStream()), 0, Long.MAX_VALUE);
}

il a également le potentiel de faire beaucoup mieux que son java.io alternative. (Toutefois, il s'avère que la mise en œuvre de Files.copy() mai choisissez de déléguer à cette méthode au lieu d'exécuter la copie elle-même.)


voici un exemple de service JavaFX réutilisable qui vous permet de récupérer une ressource à partir d'internet et de la sauvegarder dans votre système de fichiers local, avec une fin gracieuse automatique si l'opération prend trop de temps.

  • la tâche de service (générée par createTask() ) est l'utilisateur de l'API file-channel.
  • Un ScheduledExecutorService distinct est utilisé pour gérer la contrainte de temps.
  • toujours s'en tenir au bonnes pratiques pour étendre Service .
  • Si vous choisissez d'utiliser une telle méthode, vous ne serez pas en mesure de retracer la progression de la tâche.
  • si la connexion devient indisponible, transferFrom() devrait éventuellement revenir sans lancer exception.

pour démarrer le service (peut être fait à partir de n'importe quel fil):

DownloadService downloadService = new DownloadService();
downloadService.setRemoteResourceLocation(new URL("http://speedtest.ftp.otenet.gr/files/test1Gb.db"));
downloadService.setPathToLocalResource(Paths.get("C:", "test1Gb.db"));
downloadService.start();

et ensuite de l'annuler (sinon elle sera automatiquement annulée après l'expiration du délai):

downloadService.cancel();

notez que le même service peut être réutilisé, assurez-vous de le réinitialiser avant de recommencer:

downloadService.reset();

Voici la classe DownloadService :

public class DownloadService extends Service<Void> {

    private static final long TIME_BUDGET = 2; // In seconds

    private final ScheduledExecutorService watchdogService =
            Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
                private final ThreadFactory delegate = Executors.defaultThreadFactory();

                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = delegate.newThread(r);
                    thread.setDaemon(true);
                    return thread;
                }
            });
    private Future<?> watchdogThread;

    private final ObjectProperty<URL> remoteResourceLocation = new SimpleObjectProperty<>();
    private final ObjectProperty<Path> pathToLocalResource = new SimpleObjectProperty<>();

    public final URL getRemoteResourceLocation() {
        return remoteResourceLocation.get();
    }

    public final void setRemoteResourceLocation(URL remoteResourceLocation) {
        this.remoteResourceLocation.set(remoteResourceLocation);
    }

    public ObjectProperty<URL> remoteResourceLocationProperty() {
        return remoteResourceLocation;
    }

    public final Path getPathToLocalResource() {
        return pathToLocalResource.get();
    }

    public final void setPathToLocalResource(Path pathToLocalResource) {
        this.pathToLocalResource.set(pathToLocalResource);
    }

    public ObjectProperty<Path> pathToLocalResourceProperty() {
        return pathToLocalResource;
    }

    @Override
    protected Task<Void> createTask() {
        final Path pathToLocalResource = getPathToLocalResource();
        final URL remoteResourceLocation = getRemoteResourceLocation();
        if (pathToLocalResource == null) {
            throw new IllegalStateException("pathToLocalResource property value is null");
        }
        if (remoteResourceLocation == null) {
            throw new IllegalStateException("remoteResourceLocation property value is null");
        }

        return new Task<Void>() {
            @Override
            protected Void call() throws IOException {
                try (FileChannel channel = FileChannel.open(pathToLocalResource, StandardOpenOption.CREATE,
                                                            StandardOpenOption.WRITE)) {
                    channel.transferFrom(Channels.newChannel(remoteResourceLocation.openStream()), 0, Long.MAX_VALUE);
                }
                return null;
            }
        };
    }

    @Override
    protected void running() {
        watchdogThread = watchdogService.schedule(() -> {
            Platform.runLater(() -> cancel());
        }, TIME_BUDGET, TimeUnit.SECONDS);
    }

    @Override
    protected void succeeded() {
        watchdogThread.cancel(false);
    }

    @Override
    protected void cancelled() {
        watchdogThread.cancel(false);
    }

    @Override
    protected void failed() {
        watchdogThread.cancel(false);
    }

}
8
répondu Francesco Menzani 2017-02-21 13:41:36

il y a un aspect important qui n'est pas couvert par les autres réponses/commentaires; et c'est une fausse hypothèse de votre part:

ce que je veux, c'est qu'il tombe en panne immédiatement quand il n'y a pas de connexion internet.

ce n'est pas si facile. La machine TCP stack/state est en fait assez compliquée et dépend de votre contexte (type D'OS, implémentation de la pile TCP, paramètres du noyau, etc.)...), il peut y avoir des situations où une partition réseau a lieu et un expéditeur ne remarque pas pendant 15 ou plus minutes . Écoutez ici pour plus de détails à ce sujet.

en d'autres termes:" juste tirer la prise "n'est en aucun cas égal à" casser immédiatement " votre connexion TCP existante. Et juste pour Info: vous n'avez pas besoin de brancher les câbles manuellement pour simuler des pannes de réseau. Dans une configuration de test raisonnable, des outils comme iptables alias pare-feu peut le faire pour vous.

3
répondu GhostCat 2017-02-17 19:53:32

vous semblez avoir besoin d'un accès HTTP asynchrone/annulable qui peut être difficile.

le problème est que si la lecture décroche en attendant plus de données (le câble est tiré) il ne cessera pas jusqu'à ce que la socket meurt ou de nouvelles données viennent dans.

il y a quelques chemins que vous pouvez suivre, bricolage avec les usines de socket pour définir un bon timeout, en utilisant le client http avec timeouts et autres.

je voudrais jeter un oeil à Apache Http Composants qui possède un HTTP non bloquant basé sur des Sockets java NIO.

1
répondu minus 2017-02-17 18:01:47