2013-08-30 7 views
9

Mam wiele wątków roboczych i GUI JavaFX, który raportuje, co dzieje się w tych wątkach.Złożona współbieżność w JavaFX: użycie ObservableLists i właściwości z wielu wątków roboczych

Istnieje wiele danych udostępnionych między wątkami i należy je zwizualizować. Używam obiektów ObservableList i Property's, aby móc łatwo wyświetlać dane w JavaFX.

Zrobiłem małą przykładową aplikację, aby pokazać coś podobnego do tego, co dzieje się w mojej aplikacji. Ma 2 listy, a wątek roboczy przenosi dane z jednej listy na drugą. Ciąg statusu jest aktualizowany. Pełny przykład kodu można znaleźć na http://codetidy.com/6569/ (kod ten ulega awarii, patrz dalej)

Oto wspólne ObservableList za & Właściwości:

private ObservableList<String> newItems; 
private ObservableList<String> readyItems; 
private StringProperty status; 

Oto jak są one wykorzystywane w JavaFX:

listViewA.setItems(processor.getNewItems()); 
listViewB.setItems(processor.getReadyItems()); 
statusLabel.textProperty().bind(processor.getStatus()); 

Wątek roboczy aktualizuje te listy i właściwości, ale oczywiście musi to zrobić w wątku JavaFX, i tam wszystko staje się brzydkie. Byłoby kod gdybym nie musiał aktualizować na gwincie JavaFX:

Runnable newItemAdder = new Runnable() { 
     @Override 
     public void run() { 
      while(true) { 
       synchronized (newItems) { 
        String newItem = checkForNewItem(); //slow 
        if (newItem != null) { 
         newItems.add(newItem); 
         newItems.notify(); 
        } 
        if (newItems.size() >= 5) 
         status.set("Overload"); 
        else 
         status.set("OK"); 
       } 

       synchronized (readyItems) { 
        if (readyItems.size() > 10) 
         readyItems.remove(0); 
       } 

       try { Thread.sleep(200); } catch (InterruptedException e) { return; } 
      } 
     } 
    }; 
    new Thread(newItemAdder).start(); 

    Runnable worker = new Runnable() { 
     @Override 
     public void run() { 
      while(true) { 
       List<String> toProcess = new ArrayList<String>(); 
       synchronized (newItems) { 
        if (newItems.isEmpty()) 
         try { newItems.wait(); } catch (InterruptedException e) { return; } 
        toProcess.addAll(newItems); 
       } 

       for (String item : toProcess) { 
        String processedItem = processItem(item); //slow 
        synchronized (readyItems) { 
         readyItems.add(processedItem); 
        } 
       } 
      } 
     } 
    }; 
    new Thread(worker).start(); 

Off Oczywiście, niektóre rzeczy są łatwe do rozwiązania z Platform.runLater:

Platform.runLater(new Runnable() { 
    @Override 
    public void run() { 
     synchronized (newItems) { 
      if (newItems.size() >= 5) 
       status.set("Overload"); 
      else 
       status.set("OK"); 
     } 
    } 
}); 

To dobrze dla properties/list, do których zapisuję tylko w zadaniu, i czytane tylko w interfejsie GUI JavaFX. Ale robi to bardzo skomplikowane, aby zrobić to dla list w tym przykładzie, na którym trzeba synchronizować, czytać i pisać. Musisz dodać wiele platform.runLater i musisz zablokować, dopóki nie zakończy się zadanie "runLater". Powoduje to bardzo złożony i trudny do odczytania i napisania kod (udało mi się uzyskać ten przykład działając w ten sposób, zobacz co mam na myśli: http://codetidy.com/6570/).

Czy istnieją inne sposoby na sprawdzenie, czy mój przykład działa? Byłbym wdzięczny innego rozwiązania lub częściowe rozwiązania ...

+0

Co zmieniłeś w swoich kodach? Czy możesz je pokazać?Ostatni link JewelSea nie działa – AloneInTheDark

+0

@AloneInTheDark Rozglądam się, ale obawiam się, że nie mam oryginalnego kodu przykładowego, ani zmodyfikowanej wersji jewelsea. Wysłałem dodatkową odpowiedź z podsumowaniem rozwiązania. –

Odpowiedz

14

Tło Info

Task javadoc zawiera liczne wzorce użycia współbieżności do przekazywania danych pomiędzy wątkami w JavaFX.

Task zawiera wygodne metody przesyłania danych, takie jak updateMessage i może być używany zamiast opcji Runnable z właściwością statusu zdefiniowaną przez użytkownika.

W razie potrzeby należy rozważyć użycie struktury kolekcji zaprojektowanej do współbieżności, takiej jak BlockingQueue. Dodatkową zaletą jest to, że BlockingQueues może mieć ograniczenia wielkości, co wydaje się być czymś, co chcesz.

Niektóre ogólne porady

  1. Bądź bardzo ostrożny używając zmienny przedmiotów obserwowalnych w wielu wątkach. Łatwo jest nieumyślnie wywołać aktualizacje, które powodują warunki wyścigu, aktualizacje aktywnego wykresu sceny z wątku aplikacji i inne problemy z gwintowaniem.
  2. W miarę możliwości używaj raczej immutable data niż przedmiotów możliwych do obserwacji.
  3. Wykorzystaj niektóre narzędzia wyższego poziomu z bibliotek JavaFX concurrency i java.util.concurrent.
  4. Unikaj jawnej synchronizacji i powiadamiaj instrukcje tak bardzo, jak to możliwe.
  5. Zachowaj ostrożność podczas umieszczania synchronizacji lub innych potencjalnie blokujących instrukcji w kodzie uruchamianym w wątku aplikacji JavaFX - ponieważ może to spowodować brak reakcji interfejsu GUI.
  6. Użyj narzędzi współbieżności JavaFX tylko wtedy, gdy potrzebujesz interakcji z wątkiem JavaFX.
  7. Wykonaj wiele bardzo skomplikowanych procesów wielowątkowych poza wątkiem JavaFX za pomocą standardowych narzędzi współbieżności Java. Mieć jedną koordynację zadania JavaFX do koalescencji i kontrolowania sprzężenia zwrotnego interfejsu użytkownika.

Powyższe zasady są tylko ogólnymi wskazówkami i nie trzeba ich stosować w praktyce.

Racjonalnie złożonych próbek gwintów

  • chart renderer który pokazuje niektóre z zasad powyżej 300 do renderowania wykresów zachowując UI reaguje na postęp aktualizacji i interakcji z użytkownikiem.
+1

+1 za dobrą poradę ogólną. Konieczność elementów GUI w języku JavaFX w celu dostosowania ich do wzorców stylu JavaFX Bean wymaga, aby zawsze były zmienne. Ponadto klasy kontrolerów w JavaFX muszą być zawsze publiczne. Wymaganie dotyczące zmienności, utraty enkapsulacji i wymagania, że ​​elementy GUI mogą być modyfikowane tylko w wątku aplikacji FX, sprawia, że ​​jest to bardzo wrogie środowisko dla wysoce współbieżnych aplikacji. – scottb

+0

Twoje rozwiązanie dzieli rzeczywiste dane i dane wyświetlane w GUI i dodaje wątek, który przesyła aktualizacje z jednego do drugiego. Wydaje się, że jest to dodatkowa praca, ale myślę, że to najczystszy sposób na realizację tego typu rzeczy. To sprawia, że ​​wszystko jest ** mniej skomplikowane. Podoba mi się też użycie BlockingQueue itp., Dzięki czemu kod jest znacznie prostszy. Nie wiedziałem o tej klasie, będę musiał sprawdzić, jakie inne przydatne rzeczy są w java.util.concurrent ... Wielkie dzięki za wspaniałą odpowiedź! –

+0

Ukończyłem zmiany w mojej prawdziwej aplikacji. W zasadzie został podzielony na wielowątkowy kod bez JavaFX i wątek, który okresowo aktualizuje GUI (a nie bezpośrednio GUI, aktualizuje "model stylu JavaFX", z właściwościami i obserwowalnymi itp.). Część, która okresowo aktualizuje "model stylu javaFX" z "modelu wielowątkowego", zawiera więcej niż 300 linii kodu (ponieważ wiele danych musi być pokazanych). To dużo dodatkowego kodu, ale jest to łatwy, czytelny kod, a wielowątkowy kod jest teraz o wiele mniej skomplikowany niż był. Tak więc jestem zadowolony z tego rozwiązania. –

1

Oryginalne linki z pełnym przykładem i przykładowe rozwiązanie autorstwa jewelsea są martwe, więc dodam odpowiedź z krótkim podsumowaniem tego, co zrobiłem.

Aby było to łatwiejsze, załóżmy, że zaczynasz od 1 klasy przechowującej Twój model danych (nazwijmy to: DataModel). Instancja tej klasy jest używana przez wiele wątków, które ją zmieniają.

Teraz problemem jest to, że chcesz korzystać z modelu danych w JavaFX, ale nie można po prostu zmienić swój model danych do wykorzystania Property i ObservableList itp Jeśli tak, że słuchacze będą wywoływane z non-JavaFX wątków, i GUI związane z nimi będą zgłaszać wyjątki.

Zamiast tego należy utworzyć osobną klasę modelu danych dla javaFX. Jest to po prostu wersja oryginalna JavaFX (nazwijmy to: FXDataModel). Ta wersja zawiera te same informacje, ale używa javaFX Property i ObservableList. W ten sposób możesz powiązać swój GUI z nim.

Następnym krokiem jest okresowa aktualizacja instancji FXDataModel przy użyciu instancji DataModel. Aby to zrobić, należy dodać metodę update(DataModel dataModel) do FXDataModel, która skopiuje dane z oryginalnego modelu danych do instancji FXDataModel. Ta funkcja aktualizacji musi zawsze być wywoływana w wątku javaFX. Wreszcie, wszystko co musisz zrobić, to okresowo wywoływać tę funkcję aktualizacji.

W moim prawdziwym scenariuszu wywołuję funkcję aktualizacji co 200 ms, a to wystarcza, aby móc wyświetlić podgląd danych na żywo w modelu danych w GUI. (Sprawy stają się bardziej złożone, jeśli chcesz uzyskać więcej niż widok modelu danych i chcesz zmienić elementy z GUI, ale to nie jest coś, co muszę zrobić)