2014-10-04 46 views
1

我是Javafx的新手,並使用它開發IDE。 JavaFX面臨的問題是,我必須使用Platform.runLater()來反映來自其他線程的GUI變化。由於我正在開發的IDE使用多線程來保持最新信息,並使用Platform.runLater()使應用程序無響應。有時後臺進程必須打印出數百萬行的輸出,我認爲在多個線程嘗試執行相同操作時會導致問題。我試圖放一個計數器,這樣如果輸出大於250000行,它將在250000行之後打印輸出,否則在線程完成後會立即打印輸出,即使在這種情況下,如果兩個或多個線程試圖執行Platform.runLater()(還有其他線程會創建帶有複選框項目的樹並反映實時值)應用程序掛起,但後臺中的所有內容都保持正常運行,甚至應用程序不會拋出任何異常。在正常的java swing應用程序中,我沒有遇到類似的問題。所以我正在尋求指導來解決這些問題。有人可以給我PRO提示來解決類似問題嗎? :)開發多線程Javafx應用程序時需要注意哪些事項?

編輯在@jewelsea的請求

我試圖保持示例代碼儘可能簡單

FxUI.java

public class FxUI extends Application { 
public static TextArea outputArea; 

@Override 
public void start(Stage primaryStage) { 
    outputArea= new TextArea(); 
    Button btn = new Button(); 

    btn.setText("Start Appending Text To Text Area"); 
    btn.setOnAction(new EventHandler<ActionEvent>() { 

     @Override 
     public void handle(ActionEvent event) { 
      Thread r=new Thread(new Runnable() { 

       @Override 
       public void run() { 
       for (int i = 0; i < 10; i++) { 
       Thread t= new Thread(new simpleThread(i)); 
      t.start(); 
       try { 
        Thread.sleep(1000); 
        System.out.println("Thread Awake"); 
       } catch (InterruptedException ex) { 
        Logger.getLogger(FxUI.class.getName()).log(Level.SEVERE, null, ex); 
       } 
      } } 
      }); 
      r.start(); 
     } 
    }); 

    VBox root = new VBox(30); 
    outputArea.setWrapText(true); 
    outputArea.setPrefHeight(400); 
    root.getChildren().add(outputArea); 
    root.getChildren().add(btn); 

    Scene scene = new Scene(root, 500, 500); 

    primaryStage.setTitle("Hello World!"); 
    primaryStage.setScene(scene); 
    primaryStage.show(); 
} 

/** 
* @param args the command line arguments 
*/ 
public static void main(String[] args) { 
    launch(args); 
} 

} 

simpleThread.java

public class simpleThread implements Runnable { 

int threadnumber; 

public simpleThread(int j) { 
    threadnumber = j; 
} 

@Override 
public void run() { 
    String output = ""; 
    String content; 
    int length; 
    final String finalcontent2; 
    final int finallength2; 

    for (long i = 0L; i <= 10000; i++) { 
     final String finalcontent; 
     final int finallength; 

     if (i % 1000 == 0) { 
      output += "\nThread number = " + threadnumber + " \t Loop Counter=" + i; 
      content = FxUI.outputArea.getText() + "\n" + output; 
      length = content.length(); 
      finallength = length; 
      finalcontent = "" + content; 
      Platform.runLater(new Runnable() { 

       @Override 
       public void run() { 
        System.out.println("appending output"); 
        FxUI.outputArea.setText(finalcontent); 
        FxUI.outputArea.positionCaret(finallength); 

       } 
      }); 
     } else { 
      output += "\nThread number = " + threadnumber + " \t Loop Counter=" + i; 

     } 
     System.out.println("Thread number = " + threadnumber + " \t Loop Counter=" + i); 

    } 

} 

} 
+0

您應該提供[mcve](http://stackoverflow.com/help/mcve)。 – jewelsea 2014-10-04 16:29:51

+0

@jewelsea新的編輯,試圖複製的問題:) – DeepSidhu1313 2014-10-04 18:33:54

+0

@jewelsea任何建議? :) – DeepSidhu1313 2014-10-05 18:39:46

回答

4

我認爲這裏的第一個真正的問題是,你的代碼僅僅是大量低效的。在循環中建立一個字符串是一件非常糟糕的事情:你創建一個新的對象並且每次都複製所有的字符。此外,每次更新文本區域時,都會複製整個現有文本,通過連接其他內容創建另一個String,然後用新內容替換所有現有內容。字符串連接將以二次方式運行(因爲您每次都會增加字符串的長度),並且您將對Java的字符串實習過程造成混亂。

另外請注意,你不應該閱讀的場景圖節點的狀態的任何地方,除了FX應用程序線程,因此您的線路

 content = FxUI.outputArea.getText() + "\n" + output; 

不是線程安全的。

通常,要在循環中建立一個字符串,您應該使用StringBuilder來構建字符串內容。如果您使用的是TextArea,則它有一個appendText(...)方法,您只需更新它即可。

更新按照意見的討論:

在作出這些一般性意見,使這些改進並沒有真正讓你裏的表現是可以接受的狀態。我的觀察是,即使在線程完成後,TextArea對用戶輸入的響應也很慢。問題是(我猜)你有大量的數據實際上與場景圖的「活」部分相關聯。

這裏更好的選擇可能是使用虛擬化控件,如ListView來顯示數據。這些只有可見部分的單元格,並在用戶滾動時重用它們。這是一個例子。我添加了選擇功能和複製到剪貼板功能,因爲這是您錯過從TextAreaListView的主要功能。 (請注意,如果你有選擇的東西一個巨大的數字,String.join()方法是運行速度很慢,你可能需要創建一個後臺任務和模塊化的對話框,顯示其進度,如果這很重要。)

import java.util.concurrent.BlockingQueue; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import java.util.concurrent.LinkedBlockingQueue; 
import java.util.concurrent.atomic.AtomicBoolean; 
import java.util.concurrent.atomic.AtomicInteger; 

import javafx.application.Application; 
import javafx.application.Platform; 
import javafx.beans.binding.Bindings; 
import javafx.concurrent.Task; 
import javafx.geometry.Insets; 
import javafx.geometry.Pos; 
import javafx.scene.Scene; 
import javafx.scene.control.Button; 
import javafx.scene.control.ListView; 
import javafx.scene.control.SelectionMode; 
import javafx.scene.input.Clipboard; 
import javafx.scene.input.ClipboardContent; 
import javafx.scene.layout.BorderPane; 
import javafx.scene.layout.HBox; 
import javafx.stage.Stage; 

public class BigListBackgroundThreadDemo extends Application { 

    private static final int NUM_ITERATIONS = 10_000 ; 
    private static final int NUM_THREADS_PER_CALL = 5 ; 

    @Override 
    public void start(Stage primaryStage) { 
     ListView<String> data = new ListView<>(); 
     data.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); 
     Button startButton = new Button("Start"); 
     Button selectAllButton = new Button("Select All"); 
     Button selectNoneButton = new Button("Clear Selection"); 
     Button copyToClipboardButton = new Button("Copy to clipboard"); 
     copyToClipboardButton.disableProperty().bind(Bindings.isEmpty(data.getSelectionModel().getSelectedItems())); 

     AtomicInteger threadCount = new AtomicInteger(); 
     ExecutorService exec = Executors.newFixedThreadPool(5, r -> { 
      Thread t = new Thread(r); 
      t.setDaemon(true); 
      return t ; 
     }); 

     startButton.setOnAction(event -> { 
      exec.submit(() -> { 
       for (int i=0; i < NUM_THREADS_PER_CALL; i++) { 
        exec.submit(createTask(threadCount, data)); 
        try { 
         Thread.sleep(500); 
        } catch (InterruptedException exc) { 
         throw new Error("Unexpected interruption", exc); 
        } 
       } 
      }); 
     }); 

     selectAllButton.setOnAction(event -> { 
      data.getSelectionModel().selectAll(); 
      data.requestFocus(); 
     }); 
     selectNoneButton.setOnAction(event -> { 
      data.getSelectionModel().clearSelection(); 
      data.requestFocus(); 
     }); 

     copyToClipboardButton.setOnAction(event -> { 
      ClipboardContent clipboardContent = new ClipboardContent(); 
      clipboardContent.putString(String.join("\n", data.getSelectionModel().getSelectedItems())); 
      Clipboard.getSystemClipboard().setContent(clipboardContent); 
     }); 

     HBox controls = new HBox(5, startButton, selectAllButton, selectNoneButton, copyToClipboardButton); 
     controls.setAlignment(Pos.CENTER); 
     controls.setPadding(new Insets(5)); 

     BorderPane root = new BorderPane(data, null, null, controls, null); 


     Scene scene = new Scene(root, 800, 600); 
     primaryStage.setScene(scene); 
     primaryStage.show(); 
    } 

    private Task<Void> createTask(AtomicInteger threadCount, ListView<String> target) { 
     return new Task<Void>() { 
      @Override 
      public Void call() throws Exception { 
       int count = threadCount.incrementAndGet(); 
       AtomicBoolean pending = new AtomicBoolean(false); 
       BlockingQueue<String> messages = new LinkedBlockingQueue<>(); 
       for (int i=0; i < NUM_ITERATIONS; i++) { 
        messages.add("Thread number: "+count + "\tLoop counter: "+i); 
        if (pending.compareAndSet(false, true)) { 
         Platform.runLater(() -> { 
          pending.set(false); 
          messages.drainTo(target.getItems()); 
          target.scrollTo(target.getItems().size()-1); 
         }); 
        } 
       } 

       return null ; 
      } 
     }; 
    } 


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

_「將Runnable發佈到事件隊列,然後立即返回給調用者」_我認爲使用'javafx.concurrent'將不會幫助bcz它將返回給調用者,並且所有線程都將執行immediatley,但它會發布輸出根據它在隊列中的位置。感謝您的回覆,我們可以使用'FxUI.outputArea.getLength()'來代替'FxUI.outputArea.getText()。length()'。 – DeepSidhu1313 2014-10-08 15:35:16

+0

我認爲javafx對於小型應用程序更好,但我們不能將其視爲用於開發我正在嘗試執行的大型或超大型應用程序。我對嗎?如果你和我分享你的想法,我會很高興。我不是專家,但我從來沒有在LS或VLS揮杆應用中遇到過這樣的問題。如果javafx不能在Real-Time中顯示我們的值,javafx會有用嗎? – DeepSidhu1313 2014-10-08 15:36:28

+0

我不太瞭解你的第一條評論。至於大型應用程序,我認爲這可能取決於您的大規模意義。我使用JavaFX來顯示相當大的數據集(基因組;因此我們一次處理引用數百萬記錄順序的表),並且性能一直很好。我的觀察與你的代碼是,即使線程完成後,它實際上執行非常糟糕。所以當你有很多數據時,TextArea對UI來說可能不是一個好的選擇。考慮用'ListView'來代替實現。 – 2014-10-08 16:48:52

1

在JavaFX中,您必須在運行TaskService中執行後臺進程。通過這樣做,你將不會釋放你的GUI線程

快速的例子,如果你想要一個字符串作爲你的進程的返回值。

服務:您要使用你的服務

public class MyService extends Service<String> { 
    @Override 
    protected Task<String> createTask() { 
     return new Task<String>() { 
      @Override 
      protected String call() throws Exception { 
       //Do your heavy stuff 
       return ""; 
      } 
     }; 
    } 
} 

地點:

final MyService service = new MyService(); 

    service.setOnSucceeded(e -> { 
     //your service finish with no problems 
     service.getValue(); //get the return value of your service 
    }); 

    service.setOnFailed(e -> { 
     //your service failed 
    }); 

    service.restart(); 

您還有其他方法一樣setOnFailed,對於不同的狀態。所以實施你所需要的。 你也可以監視這個服務,但是我讓你閱讀這個文檔。它很簡單。

你也應該閱讀JavaFX concurency

+0

我想,你應該複製這些樣本與lambda,這是更短,更容易閱讀。 – 2014-10-04 10:53:38

+0

你是對的我編輯我的代碼 – 2014-10-04 10:58:34

+0

順便說一下,如果你的任務沒有返回任何東西,通常用['Void']參數化(http://docs.oracle.com/javase/6/docs/ API /爪哇/郎/ Void.html)。 – 2014-10-04 11:00:58

相關問題