Подтвердить что ты не робот

Принудительная остановка Java Files.copy() работает на внешней ветке

Ответ, казалось, был правильным решением перед Java 8: Как отменить Files.copy() в Java?

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


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

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

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

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

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


Tester (Загрузка 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:

Использование комментария @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- > Что делает java.lang.Thread.interrupt()?

4b9b3361

Ответ 1

Я настоятельно рекомендую вам использовать FileChannel. У него есть метод transferFrom(), который немедленно возвращается, когда поток, выполняющий его, прерывается. (Здесь 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.
  • Всегда придерживайтесь хороших практик для расширения 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();

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

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

}

Ответ 2

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

Я хочу, чтобы он немедленно сработал, когда нет подключения к Интернету.

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

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

Ответ 3

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

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

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

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