Forcer l'arrêt des fichiers Java.copy () s'exécute sur un thread externe


La réponse ici semblait être une solution valide avant Java 8: Comment annuler des fichiers.copier() en Java?

Mais maintenant cela ne fonctionne pas, car ExtendedCopyOption.INTERRUPTIBLE est privé.


Fondamentalement, j'ai besoin de télécharger un fichier à partir d'un URL donné et de l'enregistrer dans mon système de fichiers local en utilisant Files.copy(). Actuellement, j'utilise un service JavaFX car j'ai besoin de montrer la progression dans un ProgressBar.

Cependant, je ne sais pas comment bloquer le thread en cours d'exécution Files.copy() si l'opération prend trop long. L'utilisation de Thread.stop() n'est au moins pas souhaitée. Même Thread.interrupt() échoue.

Je veux également que l'opération se termine gracieusement si la connexion Internet devient indisponible.

Pour tester le cas où aucune connexion Internet n'est disponible, je retire mon câble Ethernet et le remets après 3 secondes. Malheureusement, Files.copy() ne revient que lorsque je remets le câble Ethernet, alors que je voudrais qu'il échoue immédiatement.

Comme je peux le voir, en interne {[4] } exécute une boucle, qui empêche le fil de sortie.


Tester(Télécharger 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:

L'utilisation de @sillyfly comment with FileChannel et la suppression de File.copy semble fonctionner uniquement avec l'appel de Thread.interrupt() mais elle ne se termine 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-ce que java.lang.Fil.interrompre () faire?

Author: Community, 2017-02-05

3 answers

, je vous encourage fortement à utiliser un FileChannel. Il a le transferFrom() méthode qui revient immédiatement lorsque le thread en cours d'exécution est interrompu. (Le Javadoc dit ici qu'il devrait soulever un ClosedByInterruptException, mais ce n'est pas le cas.)

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 fonctionner beaucoup mieux que son alternative java.io. (Cependant, il s'avère que l'implémentation de {[9] } peut choisir de déléguer à cette méthode au lieu d'effectuer réellement la copie par lui-même.)


Voici un exemple de service JavaFX réutilisable qui vous permet de récupérer une ressource sur Internet et de l'enregistrer dans votre système de fichiers local, avec une terminaison 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.
  • séparé ScheduledExecutorService est utilisé pour gérer les contraintes de temps.
  • Toujours s'en tenir aux bonnes pratiques pour étendre Service.
  • Si vous choisissez d'utiliser une méthode de haut niveau, vous ne pourrez pas suivre la progression de la tâche.
  • Si la connexion devient indisponible, transferFrom() devrait éventuellement revenir sans lancer d'exception.

Démarrer le service (peut être fait à partir de n'importe quel thread):

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();

Puis pour l'annuler (sinon il sera automatiquement annulé après l'expiration du délai):

downloadService.cancel();

Notez que le même service peut être réutilisé, assurez-vous simplement 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
Author: 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 mauvaise hypothèse de la vôtre:

Ce que je veux, c'est qu'il échoue immédiatement lorsqu'il n'y a pas de connexion Internet.

, Il n'est pas facile. La pile TCP / machine d'état est en fait une chose assez compliquée; et en fonction de votre contexte (type de système d'exploitation; implémentation de la pile TCP, paramètres du noyau, ...), il peut y avoir des situations où une partition réseau a lieu et un expéditeur ne avis de 15 minutes ou plus . Écoutezici pour plus de détails à ce sujet.

En d'autres termes: "simplement tirer la fiche" n'est pas égal à "casser immédiatement" votre connexion TCP existante. Et pour mémoire: vous n'avez pas besoin de brancher des câbles manuellement pour simuler des pannes de réseau. Dans une configuration de test raisonnable, des outils comme iptables aka firewalls peuvent le faire pour vous.

 3
Author: GhostCat, 2017-02-17 19:53:32

Vous semblez avoir besoin d'un GET HTTP Asynchrone/Annulable qui peut être difficile.

Le problème est que si la lecture s'arrête en attendant plus de données (le câble est tiré), elle ne s'arrêtera pas tant que la prise ne meurt pas ou que de nouvelles données n'arrivent pas.

Il y a quelques chemins que vous pouvez suivre, bricoler avec des usines de socket pour définir un bon délai d'attente, en utilisant le client http avec des délais d'attente et d'autres.

Je voudrais jeter un oeil à Apache Http Components {[8] } qui a HTTP non bloquant basé sur java NIO Socket.

 1
Author: minus, 2017-02-17 18:01:47