产生其他服务/任务的后台服务

时间:2015-10-20 16:24:01

标签: java javafx concurrency

我试图围绕javafx中的Service / Task以及如何将它们嵌套在一起。我的应用程序是一个简单的rss下载程序。它会下载多个Feed,并且还会在每个Feed项<link>中下载html。我希望整个下载过程是异步的(以防止GUI冻结),以及每个rss feed下载和每个html下载。我希望这个过程看起来像这样。

Application thread.
|
|
|--------Download process start(Service)
|       |
|       |
|       |----RSS download start(Service)
|       |    |(30+ Tasks that each download an individual feed.
|       |----RSS download end
|       |
|       |
|       |----HTML download start(Service)
|       |    |(100+ Tasks that each download an individual HTML page.
|       |----HTML download end
|       |
|       |
|--------Download process end.
|
|

我的代码。 downloadStart()启动了Downloader服务。

    @Override 
    public void downloadStart(List<Channel> channels) {
        Downloader downloader = new Downloader(channels);
        downloader.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
            @Override
            public void handle(WorkerStateEvent t) {
                List<Article> result = (List<Article>)t.getSource().getValue();
                display.printToOutput("Completed download process : " + result.size());
            }
        });
        downloader.start();
    }

Downloader班。

public class Downloader extends Service<List<Article>> {

List<Channel> channels;

public Downloader(List<Channel> channels){
    this.channels = channels;
}

public void downloadRSS() {
    for(Channel channel : channels){
        RSSDownloadService<List<Article>> downloader = new RSSDownloadService<List<Article>>(channel);
        downloader.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
            @Override
            public void handle(WorkerStateEvent t) {
                List<Article> result = (List<Article>)t.getSource().getValue();
                downloadHTML(result);
            }
        });
        downloader.start();
    }
}

private void downloadHTML(List<Article> articles){
    HTMLDownloadService<List<Article>> downloader = new HTMLDownloadService<List<Article>>(articles);
    downloader.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
        @Override
        public void handle(WorkerStateEvent t) {
            List<Article> result = (List<Article>)t.getSource().getValue();
            //how do i tell the Downloader service to return this result?
        }
    });
    downloader.start();
}

@Override
protected Task<List<Article>> createTask() {
    return new Task<List<Article>>() {
        protected List<Article> call() {
            downloadRSS();
            //i can't return anything until downloadHTML() finishes!!
        }
    };
}

}

问题:启动Downloader服务后,它的createTask()方法调用downloadRSS()并期望返回值。但是,downloadRSS()方法不会返回任何内容,它会启动RSSDownloadService。当RSSDownloadService成功时,它会调用downloadHTML(),它会启动HTMLDownloadService。最后,当成功时,我想结束整个Downloader服务并返回List篇文章。我不知道该怎么办。

RSSDownloadServiceHTMLDownloadService工作正常。它们对我来说很简单,因为它们调用一个带有返回值的方法。但是`DownloaderService&#39;不知何故需要等待2个服务完成,并返回第2个服务成功值。

1 个答案:

答案 0 :(得分:1)

如果我理解正确,您希望downloadRSS()返回List<Article>,其中包含Article生成的HTMLDownloadService返回的所有列表中的所有public List<Article> downloadRSS() { List<Article> mainList = Collections.synchronizedList(new ArrayList<>()); CountDownLatch latch = new CountDownLatch(channels.size()); for(Channel channel : channels){ RSSDownloadService<List<Article>> downloader = new RSSDownloadService<List<Article>>(channel); downloader.setOnSucceeded(new EventHandler<WorkerStateEvent>() { @Override public void handle(WorkerStateEvent t) { List<Article> result = (List<Article>)t.getSource().getValue(); downloadHTML(result, mainList, latch); } }); downloader.setOnFailed(t -> { // handle error if neccessary... latch.countDown(); }); downloader.start(); } latch.await(); // return a regular list, don't need the overhead of synchronization any more: return new ArrayList<>(mainList); } private void downloadHTML(List<Article> articles, List<Article> mainList, CountDownLatch latch){ HTMLDownloadService<List<Article>> downloader = new HTMLDownloadService<List<Article>>(articles); downloader.setOnSucceeded(new EventHandler<WorkerStateEvent>() { @Override public void handle(WorkerStateEvent t) { List<Article> result = (List<Article>)t.getSource().getValue(); mainList.addAll(result); latch.countDown(); } }); downloader.setOnFailed(t -> { // handle error if needed... latch.countDown(); }); downloader.start(); } @Override protected Task<List<Article>> createTask() { return new Task<List<Article>>() { protected List<Article> call() { return downloadRSS(); } }; } 。< / p>

我认为以下是您想要的:

public class Downloader extends Task<List<Article>> {

    private final List<Channel> channels ;

    public Downloader(List<Channel> channels) {
        this.channels = channels ;
    }

    @Override
    public List<Article> call() throws Exception {
        return channels.parallelStream()
            .flatMap(channel -> getRssList(channel).parallelStream())
            .flatMap(rss -> getHtmlList(rss).stream())
            .collect(Collectors.toList());
    }

    private List<Article> getRssList(Channel channel) {
        // this runs in its own thread, return List<Article> for given channel
    }

    private List<Article> getHtmlList(Article rss) {
        // this runs in its own thread, return List<Article> for given rss
    }
}

这可能是一种更好的方法,使用Java 8流和内置并行化来管理大部分线程:

List<Channel> channels = ... ;
Downloader downloader = new Downloader(channels);
downloader.setOnSucceeded(e -> {
    List<Article> articles = downloader.getValue();
    // update UI with articles...
});
Thread t = new Thread(downloader);
t.setDaemon(true) ; // will not prevent application exit...
t.start();

然后你在ui中所需要的只是:

var config = require('../config');
var AWS = require('aws-sdk');
AWS.config.update({
  accessKeyId: config.AWS_accessKeyId,
  secretAccessKey: config.AWS_secretAccessKey,
  region: 'eu-west-1'
});
var s3 = new AWS.S3();
var fs = require('fs');
var ffmpeg = require('fluent-ffmpeg');

exports.generateVideoThumbnail = function(fileId, url, done) {
  var params = {
    Bucket: config.AWS_bucket,
    Key: url
  };
  var input = s3.getObject(params);
  var stream = fs.createWriteStream('./screenshot.png');
  return ffmpeg(input).screenshots({
    timestamps: ['0.1', '0.2'],
    size: '200x200'
  }).output('./screenshot.png').output(stream).on('error', function(err) {
    return done(err);
  }).on('end', function() {
    input.close();
    var _key = "files/" + fileId + "/thumbnail.png";
    return s3.putObject({
      Bucket: config.AWS_bucket,
      Key: _key,
      Body: stream,
      ContentType: 'image/jpeg'
    }, function(err) {
      return done(err);
    });
  });
};