本文介绍了强制停止在外部线程上运行的Java Files.copy()的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Java 8之前,答案似乎是一个有效的解决方案:

解决方案

我强烈建议您使用。
它有方法,当运行它的线程中断时立即返回。
(Javadoc在这里表示它应该引发一个 ClosedByInterruptException ,但它不会。)



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



$ b

它也有可能比 java.io 替代。
(但是,事实证明, Files.copy()的实现可能会选择委托给此方法,而不是实际执行副本。)






下面是一个可重复使用的JavaFX服务示例,它允许您从Internet获取资源并将其保存到本地文件 - 系统,如果操作时间过长,自动优雅终止。




  • 服务任务(由 createTask() )是文件通道API的用户。

  • 单独的用于处理时间约束。 >
  • 始终坚持以扩展。

  • 如果你选择使用这样的高级方法,将无法追踪任务的进度。
  • 如果连接不可用, 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(); 

请注意,相同的服务可以重复使用,只需确保在重新启动之前将其重置: p>

  downloadService.reset(); 

以下是 DownloadService 类:

  public class DownloadService extends Service< Void> {

private static final long TIME_BUDGET = 2; //在秒

私人最终ScheduledExecutorService的watchdogService =
Executors.newSingleThreadScheduledExecutor(新的ThreadFactory(){
私人最终的ThreadFactory代表= Executors.defaultThreadFactory();

@Override
public Thread newThread(Runnable r){
Thread thread = delegate.newThread(r);
thread.setDaemon(true);
return thread;
}
});
私人未来<?> 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
保护任务<空白> createTask(){
最终路径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);


返回新的任务< Void>(){
@Override
保护无效call()抛出IOException {
try(FileChannel channel = FileChannel .open(pathToLocalResource,StandardOpenOption.CREATE,
StandardOpenOption.WRITE)){
channel.transferFrom(Channels.newChannel(remoteResourceLocation.openStream()),0,Long.MAX_VALUE);
}
返回null;
}
}; ($)b

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

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

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

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

}


The answer here seemed to be a valid solution before Java 8:How to cancel Files.copy() in Java?

But now it doesn't work, because ExtendedCopyOption.INTERRUPTIBLE is private.


Basically, I need to download a file from some given URL and save it to my local file-system using Files.copy().Currently, I am using a JavaFX Service because I need to show the progress in a ProgressBar.

However, I don't know how to block the thread running Files.copy() if the operation takes too long.Using Thread.stop() is at least not wanted. Even Thread.interrupt() fails.

I also want the operation to terminate gracefully if the internet connection becomes unavailable.

To test the case when no internet connection is available, I'm removing my ethernet cable and putting it back after 3 seconds.Unfortunately, Files.copy() returns only when I put back the ethernet cable, while I would like it to fail immediately.

As I can see, internally Files.copy() is running a loop, which prevents the thread from exiting.


Tester(Downloading 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:

Using @sillyfly comment with FileChannel and removing File.copy seems to work only with calling Thread.interrupt() but it is not exiting when the internet is not available..

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

    };
    }

}

Interesting questions:

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

解决方案

I strongly encourage you to use a FileChannel.It has the transferFrom() method which returns immediately when the thread running it is interrupted.(The Javadoc here says that it should raise a ClosedByInterruptException, but it doesn't.)

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

It also has the potential to perform much better than its java.io alternative.(However, it turns out that the implementation of Files.copy() may elect to delegate to this method instead of actually performing the copy by itself.)


Here's an example of a reusable JavaFX Service that lets you fetch a resource from the internet and save it to your local file-system, with automatic graceful termination if the operation takes too long.

  • The service task (spawned by createTask()) is the user of the file-channel API.
  • A separate ScheduledExecutorService is used to handle the time constraint.
  • Always stick to the good practices for extending Service.
  • If you choose to use such an high-level method, you won't be able to track down the progress of the task.
  • If the connection becomes unavailable, transferFrom() should eventually return without throwing an exception.

To start the service (may be done from any 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();

and then to cancel it (otherwise it will be automatically cancelled after the time expires):

downloadService.cancel();

Note that the same service can be reused, just be sure to reset it before starting again:

downloadService.reset();

Here is the DownloadService class:

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

}

这篇关于强制停止在外部线程上运行的Java Files.copy()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-20 06:18
查看更多