2013-08-30 6 views
9

私は複数のワーカースレッドと、これらのスレッドで何が起きているかを報告しているJavaFX GUIを持っています。JavaFXの複雑な並行性:ObservableListsと複数のワーカースレッドのプロパティを使用

スレッド間で多くのデータが共有されており、視覚化する必要があります。だから私はObservableListとPropertyを使って、JavaFXでデータを簡単に表示できるようにしています。

私は小さなサンプルアプリケーションを作成して、アプリケーションで起こったことに似たものを表示しました。 これは2つのリストを持ち、ワーカースレッドはあるリストから別のリストにデータを移動します。ステータス文字列は最新の状態に保たれます。 完全なサンプルコードは、http://codetidy.com/6569/で発見することができます(このコードはクラッシュします、後で参照)。ここ

は、共有ObservableListの&プロパティは以下のとおりです。

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

は、ここで彼らはJavaFXの中で使用されている方法は次のとおりです。

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

ワーカースレッドはこれらのリストとプロパティを更新しますが、もちろん、これはJavaFXスレッドで行う必要があります。 私は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(); 

コースオフ、いくつかのものがPlatform.runLaterで解決するのは簡単です:

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

の罰金だこと私がタスクに書き込むプロパティ/リストは、JavaFX GUIでのみ読み込みます。 しかし、この例では、同期、読み取り、書き込みが必要なリストの場合は非常に複雑になります。 Platform.runLaterをたくさん追加する必要があり、 "runLater"タスクが終了するまでブロックする必要があります。この結果、コードは非常に複雑で読みにくくなります(私はこのような方法でこの例を実行することができました。私が意味するところはhttp://codetidy.com/6570/です)。

私のサンプルを動作させる他の方法はありますか?私は

+0

コードを変更しましたか?あなたはそれらを見せることができますか?JewelSeaの最後のリンクは機能しません – AloneInTheDark

+0

@AloneInTheDark私は見回しましたが、元のサンプルコードもjewelseaの修正版もありません。私は解決策の要約とともに余分な回答を掲示しました。 –

答えて

14

背景情報

Task javadoc JavaFXの内のスレッド間でデータを受け渡すための多数の同時実行性の使用パターンが含まれています...他のソリューションまたは部分的な解決策をいただければと思います。

TaskにはupdateMessageなどの便利なデータ転送方法が含まれており、ユーザー定義のステータスプロパティを持つRunnableの代わりに使用できます。

BlockingQueueのように、並行処理用に設計されたコレクション構造を使用することを検討してください。もう一つの利点は、BlockingQueuesにサイズの制限があることです。

いくつかの一般的なアドバイス

  1. 複数のスレッドで変更可能な観察可能なアイテムを使用する場合には、十分注意してください。競合状態、アプリケーション・スレッドからのアクティブ・シーン・グラフへの更新、およびその他のスレッド問題を引き起こす更新を誤って起動することは簡単です。
  2. 可観測可能な項目ではなく、可能な限りimmutable dataを使用してください。
  3. JavaFX concurrencyおよびjava.util.concurrentライブラリの上位レベルのユーティリティを利用します。
  4. 明示的な同期を避け、可能な限り通知文を通知してください。
  5. JavaFXアプリケーションスレッドで実行されるコードに、同期やその他の潜在的なブロック文を配置しないように注意してください.GUIが応答しなくなる可能性があります。
  6. JavaFXスレッドとの対話が必要な場合にのみ、JavaFX並行性ユーティリティを使用します。
  7. 標準のJava並行処理ユーティリティを使用して、JavaFXスレッドから非常に複雑なマルチスレッド処理をたくさん実行します。 UIフィードバックを統合して制御するための単一の調整JavaFXタスクを用意してください。

上記は単なる経験則であり、教科書に従う必要はありません。

合理的コンプレックススレッディング・サンプル

  • まだ更新とユーザーとの対話を進めるに応答UIを維持しながら300のチャートを描画するために上記の原則のいくつかを示していchart renderer。完全な例とjewelseaにより、例えば、溶液で
+1

+1よくある一般的なアドバイス。 JavaFX Beanスタイルのパターンに準拠するためには、JavaFXのGUI要素の必要性が常に変化する必要があります。さらに、JavaFXのコントローラクラスは常にpublicでなければなりません。変更可能性、カプセル化の喪失、およびFXアプリケーションスレッド上でしかGUI要素を変更できないという要件は、非常に並行しているアプリケーションにとっては非常に敵対的な環境になります。 – scottb

+0

ソリューションは、GUIに表示された実際のデータとデータを分割し、一方から他方に更新を送信するスレッドを追加します。それは余分な仕事と思われますが、私はそれがこの種のものを実装する最もクリーンな方法だと思います。それは** **多くの**複雑さをそれほど複雑にしません。私はあなたが使用するBlockingQueueなども好きです、それはコードをもっと簡単にします。私はこのクラスについて知らなかった、java.util.concurrentにある他の有用なものが何かをチェックしなければならないだろう...この素晴らしい答えのためにたくさんありがとう! –

+0

私は実際のアプリケーションの変更を完了しました。基本的には、JavaFXとGUIを定期的に更新するスレッド(GUIではなく、プロパティとObservableListsなどで "JavaFXスタイルモデル"を更新する)を持たないマルチスレッドコードに分割されています。 「マルチスレッドモデル」から「javaFXスタイルモデル」を定期的に更新する部分は、300行以上のコードです(多くのデータを表示する必要があるため)。これは余分なコードがたくさんありますが、それは簡単で読みやすいコードであり、マルチスレッドコードは以前よりずっと複雑ではありません。だから私はこの解決策に満足しています。 –

1

元のリンクが死んでいるので、私は私がやってしまったかの短い要約と答えを追加します。

これを簡単にするには、データモデルを保持する1つのクラス(最初はDataModelとしましょう)から始めましょう。このクラスのインスタンスは、それを変更する複数のスレッドによって使用されます。

さて問題は、JavaFXの中でデータモデルを使用したいということですが、あなたは単にあなたが、リスナーが非JavaFXのスレッドから呼び出されることを行う場合PropertyObservableListなどを使用するように、データモデルを変更することはできません、それらにバインドされたGUIは例外をスローします。

代わりに、javaFX用に別々のデータモデルクラスを作成する必要があります。これは、元のバージョンのJavaFXバージョンです(FXDataModelとしましょう)。したがって、このバージョンには同じ情報が含まれていますが、javaFX PropertyObservableListが使用されています。このようにして、GUIをバインドすることができます。

次のステップは、DataModelインスタンスを使用してFXDataModelインスタンスを定期的に更新することです。 update(DataModel dataModel)メソッドをFXDataModelに追加すると、元のデータモデルのデータがFXDataModelインスタンスにコピーされます。この更新関数は、常にjavaFXスレッドで呼び出される必要があります。最後に、更新機能を定期的に呼び出すだけです。

私の実際のシナリオでは、更新機能は200ミリ秒ごとに呼び出され、それはGUIのデータモデルのライブビューを表示できるほど十分です。 (データモデルのビュー以上のものを作成し、GUIから変更する場合は複雑ですが、これは必要なものではありません)

関連する問題