2017-02-05 2 views
22

ответ здесь, казалось, правильное решение, прежде чем Java 8: How to cancel Files.copy() in Java?Force остановка Java Files.copy() работает на внешней резьбой

Но теперь он не работает, потому что ExtendedCopyOption.INTERRUPTIBLE является частным.


В принципе, мне нужно, чтобы загрузить файл с некоторой заданной URL и сохранить его в моей локальной файловой системы с помощью Files.copy(). В настоящее время я использую службу JavaFX, потому что мне нужно показать прогресс в ProgressBar.

Однако, я не знаю, как заблокировать поток, выполняющийся Files.copy(), если операция занимает слишком много времени. Использование Thread.stop() по крайней мере не требуется. Даже Thread.interrupt() не удается.

Я также хочу, чтобы операция прекратилась изящно, если интернет-соединение становится недоступным.

Чтобы проверить случай, когда интернет-соединение недоступно, я удаляю свой кабель Ethernet и верну его обратно через 3 секунды. К сожалению, Files.copy() возвращает только тогда, когда я вернул кабель Ethernet, хотя мне бы хотелось, чтобы он немедленно сработал.

Как я вижу, внутри Files.copy() работает петля, которая предотвращает выход нити.


Tester (Загрузка OBS Студия ехе):

/** 
* @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:

Использование @sillyfly комментарий с FileChannel и удаление File.copy, кажется, работает только с заходом Thread.interrupt(), но это не выход, когда Интернет недоступен.

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; 
     } 

    }; 
    } 

} 

Интересные вопросы:

1->What does java.lang.Thread.interrupt() do?

+0

Если нить не закончилась в цикле, она автоматически остановится. Теперь, если поток продолжает работать, это означает, что он находится в цикле, и вы, похоже, хотите использовать «Thread».interrupt() ', чтобы остановить его. Но это может работать, только если в потоке есть прерывание, например 'Thread.pause (x)'. В противном случае JVM не знает, когда прервать поток, это было бы не здорово, если бы это произошло в середине операции записи файлов. – Voltboyy

+3

Я не уверен, но похоже, что вам нужно будет использовать что-то вроде [FileChannel'] (https://docs.oracle.com/javase/8/docs/api/java/nio/channels/ FileChannel.html), аналогично тому, что описано в [этом вопросе] (http://stackoverflow.com/questions/35875142/how-to-cancel-files-copy-in-java-while-not-using-a-non -api-класс/35877509). Это похоже на излишний, хотя, возможно, есть более простой способ. – Itai

+2

@sillyfly Я думаю, что ваш вариант будет работать отлично, и для тестирования потери соединения может использоваться тайм-аут, несколько как в [этом вопросе] (http://stackoverflow.com/questions/6529733/how-to-use-urlconnection- Тайм-аут). – Voltboyy

ответ

6

Я настоятельно рекомендую вам использовать FileChannel. У него есть метод transferFrom(), который немедленно возвращается, когда поток, выполняющий его, прерывается. (The Javadoc здесь говорит, что он должен поднять ClosedByInterruptException, но это не так.)

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

Он также имеет потенциал для выполнения гораздо лучше, чем его java.io альтернативы. (Тем не менее, оказывается, что реализация Files.copy() может делегировать этому методу вместо фактического выполнения копирования сам по себе.)


Вот пример многоразовой JavaFX службы, которая позволяет извлечь ресурс из Интернета и сохранить его в локальной файловой системе с автоматическим изящным завершением, если операция занимает слишком много времени.

  • Задача службы (порожденная createTask()) является пользователем API файлового канала.
  • Отдельный ScheduledExecutorService используется для управления временными ограничениями.
  • Всегда придерживайтесь good practices для расширения Service.
  • Если вы решите использовать такой метод высокого уровня, вы не сможете отслеживать ход выполнения задачи.
  • Если соединение становится недоступным, transferFrom() должно в конечном итоге вернуться без исключения исключения.

Чтобы запустить службу (может быть сделано из любого потока):

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

, а затем отменить его (в противном случае он будет автоматически отменен после того, как истечет время):

downloadService.cancel(); 

Обратите внимание, что эту же услугу можно использовать повторно, просто убедитесь, что она была перезапущена до начала повторного запуска:

downloadService.reset(); 

Вот 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); 
    } 

} 
1

Вы, кажется, нужен асинхронный/Cancellable HTTP GET, который может быть жестким.

Проблема заключается в том, что если прочитать киоски ждут больше данных (кабель вытягивается), он не уйдет, пока либо сокет не умирает или новые данные поступают в системе.

Есть несколько пути, который вы могли бы следовать, мастерить с фабриками сокетов, чтобы установить хороший тайм-аут, используя http-клиент с тайм-аутами и другими.

Я бы посмотрел на Apache Http Components, который не блокирует HTTP на основе java NIO Sockets.

3

Существует один важный аспект, не охватываемый другими ответами/комментариями; и это неправильное ваше предположение:

Что я хочу - это немедленно сбой, когда нет подключения к Интернету.

Не все так просто. Стек TCP/state машина на самом деле довольно сложная вещь; и в зависимости от вашего контекста (тип ОС, реализация стека TCP, параметры ядра, ...) могут быть ситуации, когда происходит разделение сети, и отправитель не замечает 15 или более минут. Прослушать here для получения более подробной информации.

Другими словами: «просто вытаскивание вилки» никоим образом не означает «немедленно нарушить» существующее TCP-соединение. И только для записи: вам не нужно вручную подключать кабели для имитации сбоев в сети. В разумной тестовой настройке такие инструменты, как iptables aka firewalls могут сделать это за вас.