Multithreading Propper JavaFX线程实现
在我的GUI中,我有一个TableView,在一个名为PathGetter的类将加载的文件加载到ObservableArrayList中之后,它应该显示一个加载文件的列表,但我无法正确地实现该任务 这是JavaFX类中的重要部分Multithreading Propper JavaFX线程实现,multithreading,javafx,concurrency,task,Multithreading,Javafx,Concurrency,Task,在我的GUI中,我有一个TableView,在一个名为PathGetter的类将加载的文件加载到ObservableArrayList中之后,它应该显示一个加载文件的列表,但我无法正确地实现该任务 这是JavaFX类中的重要部分 browseButton.setOnAction(event -> { File dir = folderPicker.showDialog(bwindow); if(dir != null){
browseButton.setOnAction(event -> {
File dir = folderPicker.showDialog(bwindow);
if(dir != null){
directoryLocation.setText(String.valueOf(dir));
bottom.getChildren().add(new javafx.scene.control.Label("Loading Tracks"));
//PathGetter.getPath(directoryLocation.getText());
PathGetter task = new PathGetter(directoryLocation.getText());
Thread th = new Thread(task);
try {
pjesme = FXCollections.observableArrayList();
} catch (Exception e) {
e.printStackTrace();
}
selection.setItems(pjesme);
chSelAll.setDisable(false);
chSelIncomplete.setDisable(false);
chSelNoCover.setDisable(false);
}
});
这是一门应该学习的课程
public class PathGetter extends Task<ObservableList<Track>> {
static boolean getSubDirs;
static ArrayList <Track> allFiles;
public static int trNr = 0;
private static String fullPath;
public PathGetter(String path) {
fullPath = path;
}
public static int getTrNr() {
return trNr;
}
public static void setTrNr(int trNr) {
PathGetter.trNr = trNr;
}
public static boolean isSupported (File f){
//supported file types
if(String.valueOf(f).endsWith(".flac") || String.valueOf(f).endsWith(".mp3") || String.valueOf(f).endsWith(".aiff") || String.valueOf(f).endsWith(".ogg") || String.valueOf(f).endsWith(".mp4")){
return true;
}else{
return false;
}
}
@Override
protected ObservableList<Track> call() throws Exception {
getSubDirs = Browser.chSubDirs.isSelected();
allFiles = new ArrayList<Track>();
Queue<File> dirs = new LinkedList<File>();
dirs.add(new File(fullPath));
while (!dirs.isEmpty()) {
for (File f : dirs.poll().listFiles()) {
if (f.isDirectory() && getSubDirs == true) {
dirs.add(f);
} else if (f.isFile() && isSupported(f)) {
allFiles.add(new Track(f));
setTrNr(getTrNr()+1);
}
}
}
ObservableList<Track> returnList = FXCollections.observableArrayList(allFiles);
return returnList;
}
}
公共类PathGetter扩展任务{
静态布尔函数;
静态ArrayList所有文件;
公共静态int trNr=0;
私有静态字符串完整路径;
公共路径(字符串路径){
完整路径=路径;
}
公共静态int getTrNr(){
返回trNr;
}
公共静态无效设置trNr(内部trNr){
PathGetter.trNr=trNr;
}
公共静态布尔值isSupported(文件f){
//支持的文件类型
if(String.valueOf(f).endsWith(“.flac”)| String.valueOf(f).endsWith(“.mp3”)| String.valueOf(f).endsWith(“.aiff”)| String.valueOf(f).endsWith(“.ogg”)| String.valueOf(f).endsWith(.mp4”)){
返回true;
}否则{
返回false;
}
}
@凌驾
受保护的ObservableList调用()引发异常{
getSubDirs=Browser.chSubDirs.isSelected();
allFiles=new ArrayList();
Queue dirs=new LinkedList();
添加(新文件(完整路径));
而(!dirs.isEmpty()){
对于(文件f:dirs.poll().listFiles()){
if(f.isDirectory()&&getSubDirs==true){
直接添加(f);
}else if(f.isFile()&&isSupported(f)){
添加(新曲目(f));
setTrNr(getTrNr()+1);
}
}
}
ObservableList returnList=FXCollections.observableArrayList(所有文件);
退货清单;
}
}
我不明白如何让TableView等待任务完成,而不阻塞整个JavaFX线程,这基本上违背了任务的目的。我希望它能够实时显示进度,只需显示当时添加的曲目数量。您可以在列表中添加一个监听器
pjesme.addListener(new ListChangeListener<Track>() {
@Override
public void onChanged(ListChangeListener.Change<? extends Track> c) {
/* Do your Stuff in the gui. Like having the status bar or things */
}
});
pjesme.addListener(新的ListChangeListener(){
@凌驾
public void onChanged(ListChangeListener.ChangeJavaFX有两个特定的线程规则:
必须在FX应用程序线程上对UI进行任何更改。不这样做将导致运行时抛出IllegalStateException
s,或者可能使UI处于不一致的状态,可能在将来的任意时间点导致不可预测的行为
任何运行时间较长的代码都应在后台线程上执行。不这样做将导致UI在代码运行时变得无响应
此外,还有一条关于线程的一般规则:
访问多线程中的可变状态时必须小心。特别是,应确保给定线程中的操作是原子的,并且可能需要特别小心,以确保在一个线程中对数据状态所做的更改对另一个线程可见
正确处理最后一部分尤其具有挑战性。此外,在UI环境中使用后台线程时,您几乎总是希望在后台线程和UI线程之间共享过程的结果,有时还希望共享过程中计算的数据。因为这很具挑战性,JavaFX提供了Task
类(以及一些其他相关类),负责处理其中更复杂的部分,以覆盖大多数用例
特别是,任务
类公开了各种属性(包括状态
,进度
,消息
,以及值
),以及线程安全的updateXXX
方法。从任何线程调用更新方法都是安全的,并且都将确保在UI线程上更新属性,并限制对它们的更新数量(如果更新发生在UI更新时间内,则基本上将更新合并在一起)。这意味着可以安全地从后台线程调用update
方法,并观察UI线程中的属性。此外,您可以随时调用这些方法,而不会“淹没”UI线程并使其失去响应
Task
类还公开用于从一种状态转换到另一种状态的处理程序,例如setOnSucceeded
(在任务正常完成时调用)和setOnFailed
(在任务引发异常时调用)。这些处理程序也在FX应用程序线程上处理
您的任务子类可以:
使用message属性更新已处理的曲目数
返回生成的轨迹列表
从UI代码中,您可以将标签文本绑定到消息属性。您还可以使用ononSucceeded
处理程序在任务完成时更新UI
为了确保线程之间不共享可变状态(由任务
机制正确管理的状态除外),您应该正确封装类。这意味着不公开任务本身操纵的任何状态。您的任何状态都不应该是静态
(无论如何,没有明显的理由让你这么做)
因此,我将把任务写如下:
public class PathGetter extends Task<ObservableList<Track>> {
private final boolean getSubDirs;
private final String fullPath;
public PathGetter(String path, boolean getSubDirs) {
fullPath = path;
this.getSubDirs = getSubDirs ;
}
public static boolean isSupported (File f){
String fileName = f.toString();
//supported file types
return fileName.endsWith(".flac")
|| fileName.endsWith(".mp3")
|| fileName.endsWith(".aiff")
|| fileName.endsWith(".ogg")
|| fileName.endsWith(".mp4") ;
}
@Override
protected ObservableList<Track> call() throws Exception {
List<Track> allFiles = new ArrayList<Track>();
Queue<File> dirs = new LinkedList<File>();
dirs.add(new File(fullPath));
while (!dirs.isEmpty()) {
for (File f : dirs.poll().listFiles()) {
if (f.isDirectory() && getSubDirs) {
dirs.add(f);
} else if (f.isFile() && isSupported(f)) {
allFiles.add(new Track(f));
updateMessage("Number of tracks processed: "+allFiles.size());
}
}
}
ObservableList<Track> returnList = FXCollections.observableArrayList(allFiles);
return returnList;
}
}
但现在的问题是,您正在一个线程中修改列表,并在另一个线程中观察它。ObservableList
的实现不是线程安全的……因此,要实现这一点,您需要使用Platform.runLater()
将修改列表的所有调用包装到任务中。例如:“警告:不要将可变状态传递给任务,然后从后台线程对其进行操作。”。问题:您似乎正在跟踪我处理的文件计数
public class PathGetter extends Task<ObservableList<Track>> {
private final boolean getSubDirs;
private final String fullPath;
public PathGetter(String path, boolean getSubDirs) {
fullPath = path;
this.getSubDirs = getSubDirs ;
}
public static boolean isSupported (File f){
String fileName = f.toString();
//supported file types
return fileName.endsWith(".flac")
|| fileName.endsWith(".mp3")
|| fileName.endsWith(".aiff")
|| fileName.endsWith(".ogg")
|| fileName.endsWith(".mp4") ;
}
@Override
protected ObservableList<Track> call() throws Exception {
List<Track> allFiles = new ArrayList<Track>();
Queue<File> dirs = new LinkedList<File>();
dirs.add(new File(fullPath));
while (!dirs.isEmpty()) {
for (File f : dirs.poll().listFiles()) {
if (f.isDirectory() && getSubDirs) {
dirs.add(f);
} else if (f.isFile() && isSupported(f)) {
allFiles.add(new Track(f));
updateMessage("Number of tracks processed: "+allFiles.size());
}
}
}
ObservableList<Track> returnList = FXCollections.observableArrayList(allFiles);
return returnList;
}
}
browseButton.setOnAction(event -> {
File dir = folderPicker.showDialog(bwindow);
if(dir != null){
directoryLocation.setText(String.valueOf(dir));
Label label = new Label("Loading Tracks");
bottom.getChildren().add(label);
PathGetter task = new PathGetter(directoryLocation.getText(), Browser.chSubDirs.isSelected());
Thread th = new Thread(task);
// keep label showing message from task:
label.textProperty().bind(task.messageProperty());
task.setOnSucceeded(e -> {
selection.setItems(task.getValue());
chSelAll.setDisable(false);
chSelIncomplete.setDisable(false);
chSelNoCover.setDisable(false);
});
task.setOnFailed(e -> {
// handle exception ...
// and log it
task.getException().printStackTrace();
});
chSelAll.setDisable(true);
chSelIncomplete.setDisable(true);
chSelNoCover.setDisable(true);
// make sure thread doesn't prevent application exit:
th.setDaemon(true);
// set it going:
th.start();
}
});