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:
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);
}
}
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.
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.