2014-12-15 6 views
0

Я использую JavaFX's WebView для анализа веб-сайта. Сайт содержит кучу ссылок - мне нужно открыть каждый из них отдельно, в определенном порядке и получить одну информацию от каждого из них.Синхронизация последовательности асинхронных вызовов

Для того, чтобы убедиться в том, что WebView загружен весь сайт, я слушаю changed случае WebEngine и ждет newState == Worker.State.SUCCEEDED. Проблема в том, что этот вызов является асинхронным. Когда я звоню webEngine.load(firstAddress);, код немедленно возвращается и до того, как эта страница будет загружена, мой код вызовет еще webEngine.load(secondAddress); и так далее.

Я понимаю, почему так сделано (почему async лучше синхронизации), но я новичок в Java, и я не уверен, что является лучшим решением этой проблемы. Я как-то понимаю многопоточность и прочее, поэтому я уже пробовал семафор (CountDownLatch класс). Но код висит на await, и я не уверен, что я делаю неправильно.

Может кто-нибудь, пожалуйста, покажите мне, как это должно быть сделано правильно? Может быть, какой-то универсальный образец, как справляться со сценариями вроде этого?

псевдокод, что я хочу добиться:

WebEngine webEngine = new WebEngine(); 
webEngine.loadPage("http://www.something.com/list-of-cars"); 
webEngine.waitForThePageToLoad(); // I need an equivalent of this. In the real code, this is done asynchronously as a callback 
// ... some HTML parsing or DOM traversing ... 
List<String> allCarsOnTheWebsite = webEngine.getDocument()....getChildNodes()...; 
// allCarsOnTheWebsite contains URLs to the pages I want to analyze 

for (String url : allCarsOnTheWebsite) 
{ 
    webEngine.loadPage(url); 
    webEngine.waitForThePageToLoad(); // same as in line 3 

    String someDataImInterestedIn = webEngine.getDocument()....getChildNodes()...Value(); 
    System.out.println(url + " : " + someDataImInterestedIn); 
} 

System.out.println("Done, all cars have been analyzed"); 

ответ

1

Вы должны использовать прослушиватели, которые вызываются при загрузке страницы, а не блокируются, пока это не будет выполнено.

Что-то вроде:

WebEngine webEngine = new WebEngine(); 
ChangeListener<State> initialListener = new ChangeListener<State>() { 
    @Override 
    public void changed(ObservableValue<? extends State> obs, State oldState, State newState) { 
     if (newState == State.SUCCEEDED) { 
      webEngine.getLoadWorker().stateProperty().removeListener(this); 
      List<String> allCarsOnTheWebsite = webEngine.getDocument()... ; 
      loadPagesConsecutively(allCarsOnTheWebsite, webEngine); 
     } 
    } 
}; 
webEngine.getLoadWorker().addListener(initialListener);  
webEngine.loadPage("http://www.something.com/list-of-cars"); 

// ... 

private void loadPagesConsecutively(List<String> pages, WebEngine webEngine) { 
    LinkedList<String> pageStack = new LinkedList<>(pages); 
    ChangeListener<State> nextPageListener = new ChangeListener<State>() { 
     @Override 
     public void changed(ObservableValue<? extends State> obs, State oldState, State newState) { 
      if (newState == State.SUCCEEDED) { 
       // process current page data 
       // ... 
       if (pageStack.isEmpty()) { 
        webEngine.getLoadWorker().stateProperty().removeListener(this); 
       } else { 
        // load next page: 
        webEngine.load(pageStack.pop()); 
       } 
      }    
     } 
    }; 
    webEngine.getLoadWorker().stateProperty().addListener(nextPageListener); 

    // load first page (assumes pages is not empty): 
    webEngine.load(pageStack.pop()); 
} 
+0

Да, я думаю, что это лучшее решение для моей проблемы. Я думал, что код будет грязным из-за этой цепочки вызовов, но так, как вы его написали - мне это нравится! Спасибо за ваше время! – PiotrK

0

Если вы хотите, чтобы выполнить все задачи одновременно, но обрабатывать их в порядке их представления, посмотрите на следующий пример:

import java.util.ArrayList; 
import java.util.List; 
import java.util.Random; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 

import javafx.application.Application; 
import javafx.application.Platform; 
import javafx.concurrent.Task; 
import javafx.scene.Scene; 
import javafx.scene.control.ListView; 
import javafx.scene.layout.BorderPane; 
import javafx.stage.Stage; 

public class ProcessTaskResultsSequentially extends Application { 

    @Override 
    public void start(Stage primaryStage) { 
     ListView<String> results = new ListView<>(); 

     List<Task<Integer>> taskList = new ArrayList<>(); 
     for (int i = 1; i<= 10 ; i++) { 
      taskList.add(new SimpleTask(i)); 
     } 

     ExecutorService exec = Executors.newCachedThreadPool(r -> { 
      Thread t = new Thread(r); 
      t.setDaemon(true); 
      return t ; 
     }); 


     Thread processThread = new Thread(() -> { 
      for (Task<Integer> task : taskList) { 
       try { 
        int result = task.get(); 
        Platform.runLater(() -> { 
         results.getItems().add("Result: "+result); 
        }); 
       } catch (Exception e) { 
        e.printStackTrace(); 
       } 
      } 
     }); 

     processThread.setDaemon(true); 
     processThread.start(); 

     taskList.forEach(exec::submit); 

     primaryStage.setScene(new Scene(new BorderPane(results), 250, 400)); 
     primaryStage.show(); 
    } 

    public static class SimpleTask extends Task<Integer> { 
     private final int index ; 

     private final static Random rng = new Random(); 

     public SimpleTask(int index) { 
      this.index = index ; 
     } 

     @Override 
     public Integer call() throws Exception { 
      System.out.println("Task "+index+" called"); 
      Thread.sleep(rng.nextInt(1000)+1000); 
      System.out.println("Task "+index+" finished"); 
      return index ; 
     } 
    } 

    public static void main(String[] args) { 
     launch(args); 
    } 
} 
+0

Спасибо, я проанализировал код и он отлично подходит для задач, которые могут быть запущены одновременно. К сожалению, в моем случае у меня есть только один экземпляр «WebView», и мне приходится обрабатывать URL-адреса один за другим. Вы понимаете? Я вставил псевдокод того, чего хочу достичь в вопросе. – PiotrK

+0

Этот ответ на самом деле отвечает на вопрос (или лучшую интерпретацию его), как указано в вашем названии; но вы правы - это не относится к конкретному случаю для загрузки страниц последовательно в веб-представлении (потому что у вас нет доступа к фоновому потоковому механизму). Я оставлю это здесь, поскольку это может быть полезно для других. –

Смежные вопросы