我有一个应用程序,其中有一个 TreeView,其中有 TreeItems 持有 大量 叶子 TreeItem。在 TreeView 中拥有大量的 TreeItem 会显着损害应用程序的性能,为了避免这种情况,我将做的是一次只允许展开一个非叶 TreeItem,并且一旦 TreeItem 被折叠,我将清除它的子项,并在需要时异步加载它们(当用户展开 TreeItem 时)。
奇怪的问题是,在下面的这个测试中,当我第一次点击 treeitem 上的展开箭头时,子项加载正常,如果我折叠它(这将清除子项)并再次展开它,有时它会工作和其他程序开始占用 30% 的 CPU 数分钟,然后恢复运行。 更奇怪的是,如果我双击 TreeItem 将其展开(不使用箭头),即使是在第一次启动程序时,hog 也会立即启动。
我在这里可能做错了什么?
附言:
LazyTreeItem 类中的一些代码受到James_D 回答Here 的启发。
我尝试在 fx 线程上运行 loadItems 任务(不使用 ItemLoader),但没有任何区别。
使用 JAVA 8 和 JAVA 9
App.java
public class App extends Application {
private TreeView<Item> treeView = new TreeView<>();
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("TreeView Lazy Load");
primaryStage.setScene(new Scene(new StackPane(treeView), 300, 275));
initTreeView();
primaryStage.show();
}
private void initTreeView() {
treeView.setShowRoot(false);
treeView.setRoot(new TreeItem<>(null));
List<SingleItem> items = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
items.add(new SingleItem(String.valueOf(i)));
}
TreeItem<Item> parentItem = new TreeItem<>(new Item());
parentItem.getChildren().add(new LazyTreeItem(new MultipleItem(items)));
treeView.getRoot().getChildren().add(parentItem);
}
public static void main(String[] args) {
launch(args);
}
}
LazyTreeItem.java
public class LazyTreeItem extends TreeItem<Item> {
private boolean childrenLoaded = false;
private boolean isLoadingItems = false;
public LazyTreeItem(Item value) {
super(value);
// Unload data on folding to reduce memory
expandedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
flush();
}
});
}
@Override
public ObservableList<TreeItem<Item>> getChildren() {
if (childrenLoaded || !isExpanded()) {
return super.getChildren();
}
if (super.getChildren().size() == 0) {
// Filler node (will translate into loading icon in the
// TreeCell factory)
super.getChildren().add(new TreeItem<>(null));
}
if (getValue() instanceof MultipleItem) {
if (!isLoadingItems) {
loadItems();
}
}
return super.getChildren();
}
public void loadItems() {
Task<List<TreeItem<Item>>> task = new Task<List<TreeItem<Item>>>() {
@Override
protected List<TreeItem<Item>> call() {
isLoadingItems = true;
List<SingleItem> downloadSet = ((MultipleItem) LazyTreeItem.this.getValue()).getEntries();
List<TreeItem<Item>> treeNodes = new ArrayList<>();
for (SingleItem download : downloadSet) {
treeNodes.add(new TreeItem<>(download));
}
return treeNodes;
}
};
task.setOnSucceeded(e -> {
Platform.runLater(() -> {
super.getChildren().clear();
super.getChildren().addAll(task.getValue());
childrenLoaded = true;
isLoadingItems = false;
});
});
ItemLoader.getSingleton().load(task);
}
private void flush() {
childrenLoaded = false;
super.getChildren().clear();
}
@Override
public boolean isLeaf() {
if (childrenLoaded) {
return getChildren().isEmpty();
}
return false;
}
}
ItemLoader.java
public class ItemLoader implements Runnable {
private static ItemLoader instance;
private List<Task> queue = new ArrayList<>();
private Task prevTask = null;
private ItemLoader() {
Thread runner = new Thread(this);
runner.setName("ItemLoader thread");
runner.setDaemon(true);
runner.start();
}
public static ItemLoader getSingleton() {
if (instance == null) {
instance = new ItemLoader();
}
return instance;
}
public <T> void load(Task task) {
if (queue.size() < 1) {
queue.add(task);
}
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!queue.isEmpty()) {
Task task = queue.get(0);
if (task != prevTask) {
prevTask = task;
task.run();
queue.remove(task);
}
}
}
}
}
Model (Item.java, SingleItem.java, MultipleItem.java)
public class Item {
}
/****************************************************************
********** SingleItem ************
****************************************************************/
public class SingleItem extends Item {
private String id;
public SingleItem(String id) {
this.id = id;
}
public void setId(String id) {
this.id = id;
}
}
/****************************************************************
********** MultipleItem ************
****************************************************************/
public class MultipleItem extends Item {
private List<SingleItem> entries = new ArrayList<>();
public MultipleItem(List<SingleItem> entries) {
this.entries = entries;
}
public List<SingleItem> getEntries() {
return entries;
}
public void setEntries(List<SingleItem> entries) {
this.entries = entries;
}
}
最佳答案
正如@kleopatra 所指出的,这个问题是由于在选择了一个或多个项目时添加了大量 子项引起的。解决此问题的一种方法是尝试实现您自己的 FocusModel,因为默认的 FocusModel 似乎是问题的根源。另一种在我看来更简单的解决方法是在添加大量子项之前清除选择;之后,您可以重新选择之前选择的项目。
我这样做的方法是使用自定义 EventType 触发 TreeModificationEvent。此外,我决定不在我的惰性 TreeItem 中覆盖 isLeaf()。当父级 TreeItem 是惰性分支时,我发现使用占位符 TreeItem 更容易。由于有占位符,父级将自动注册为分支机构。
这是一个浏览默认 FileSystem 的示例。为了测试解决方案是否有效,我创建了一个包含 100,000 个文件的目录并打开它;我没有挂起。希望这意味着它可以适应您的代码。
注意:此示例确实会在折叠分支时删除子项,就像您在代码中所做的那样。
App.java
import java.nio.file.FileSystems;
import java.nio.file.Path;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.stage.Stage;
public class App extends Application {
private static String pathToString(Path p) {
if (p == null) {
return "null";
} else if (p.getFileName() == null) {
return p.toString();
}
return p.getFileName().toString();
}
@Override
public void start(Stage primaryStage) {
TreeView<Path> tree = new TreeView<>(new TreeItem<>());
tree.setShowRoot(false);
tree.setCellFactory(LazyTreeCell.forTreeView("Loading...", App::pathToString));
TreeViewUtils.installSelectionBugWorkaround(tree);
for (Path fsRoot : FileSystems.getDefault().getRootDirectories()) {
tree.getRoot().getChildren().add(new LoadingTreeItem<>(fsRoot, new DirectoryLoader(fsRoot)));
}
primaryStage.setScene(new Scene(tree, 800, 600));
primaryStage.show();
}
}
DirectoryLoader.java
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
import javafx.scene.control.TreeItem;
public class DirectoryLoader implements Callable<List<? extends TreeItem<Path>>> {
private static final Comparator<Path> COMPARATOR = (left, right) -> {
boolean leftIsDir = Files.isDirectory(left);
if (leftIsDir ^ Files.isDirectory(right)) {
return leftIsDir ? -1 : 1;
}
return left.compareTo(right);
};
private final Path directory;
public DirectoryLoader(Path directory) {
this.directory = directory;
}
@Override
public List<? extends TreeItem<Path>> call() throws Exception {
try (Stream<Path> stream = Files.list(directory)) {
return stream.sorted(COMPARATOR)
.map(this::toTreeItem)
.collect(Collectors.toList());
}
}
private TreeItem<Path> toTreeItem(Path path) {
return Files.isDirectory(path)
? new LoadingTreeItem<>(path, new DirectoryLoader(path))
: new TreeItem<>(path);
}
}
LoadingTreeItem.java
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.function.Supplier;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.event.EventType;
import javafx.scene.control.TreeItem;
public class LoadingTreeItem<T> extends TreeItem<T> {
private static final EventType<?> PRE_ADD_LOADED_CHILDREN
= new EventType<>(treeNotificationEvent(), "PRE_ADD_LOADED_CHILDREN");
private static final EventType<?> POST_ADD_LOADED_CHILDREN
= new EventType<>(treeNotificationEvent(), "POST_ADD_LOADED_CHILDREN");
@SuppressWarnings("unchecked")
static <T> EventType<TreeModificationEvent<T>> preAddLoadedChildrenEvent() {
return (EventType<TreeModificationEvent<T>>) PRE_ADD_LOADED_CHILDREN;
}
@SuppressWarnings("unchecked")
static <T> EventType<TreeModificationEvent<T>> postAddLoadedChildrenEvent() {
return (EventType<TreeModificationEvent<T>>) POST_ADD_LOADED_CHILDREN;
}
private final Callable<List<? extends TreeItem<T>>> callable;
private boolean needToLoadData = true;
private CompletableFuture<?> future;
public LoadingTreeItem(T value, Callable<List<? extends TreeItem<T>>> callable) {
super(value);
this.callable = callable;
super.getChildren().add(new TreeItem<>());
addExpandedListener();
}
@SuppressWarnings("unchecked")
private void addExpandedListener() {
expandedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
needToLoadData = true;
if (future != null) {
future.cancel(true);
}
super.getChildren().setAll(new TreeItem<>());
}
});
}
@Override
public ObservableList<TreeItem<T>> getChildren() {
if (needToLoadData) {
needToLoadData = false;
future = CompletableFuture.supplyAsync(new CallableToSupplierAdapter<>(callable))
.whenCompleteAsync(this::handleAsyncLoadComplete, Platform::runLater);
}
return super.getChildren();
}
private void handleAsyncLoadComplete(List<? extends TreeItem<T>> result, Throwable th) {
if (th != null) {
Thread.currentThread().getUncaughtExceptionHandler()
.uncaughtException(Thread.currentThread(), th);
} else {
Event.fireEvent(this, new TreeModificationEvent<>(preAddLoadedChildrenEvent(), this));
super.getChildren().setAll(result);
Event.fireEvent(this, new TreeModificationEvent<>(postAddLoadedChildrenEvent(), this));
}
future = null;
}
private static class CallableToSupplierAdapter<T> implements Supplier<T> {
private final Callable<T> callable;
private CallableToSupplierAdapter(Callable<T> callable) {
this.callable = callable;
}
@Override
public T get() {
try {
return callable.call();
} catch (Exception ex) {
throw new CompletionException(ex);
}
}
}
}
LazyTreeCell.java
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeView;
import javafx.util.Callback;
public class LazyTreeCell<T> extends TreeCell<T> {
public static <T> Callback<TreeView<T>, TreeCell<T>> forTreeView(String placeholderText,
Callback<? super T, String> toStringCallback) {
return tree -> new LazyTreeCell<>(placeholderText, toStringCallback);
}
private final String placeholderText;
private final Callback<? super T, String> toStringCallback;
public LazyTreeCell(String placeholderText, Callback<? super T, String> toStringCallback) {
this.placeholderText = placeholderText;
this.toStringCallback = toStringCallback;
}
/*
* Assumes that if "item" is null **and** the parent TreeItem is an instance of
* LoadingTreeItem that this is a "placeholder" cell.
*/
@Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else if (item == null && getTreeItem().getParent() instanceof LoadingTreeItem) {
setText(placeholderText);
} else {
setText(toStringCallback.call(item));
}
}
}
TreeViewUtils.java
import java.util.ArrayList;
import java.util.List;
import javafx.beans.value.ChangeListener;
import javafx.event.EventHandler;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeItem.TreeModificationEvent;
import javafx.scene.control.TreeView;
public class TreeViewUtils {
public static <T> void installSelectionBugWorkaround(TreeView<T> tree) {
List<TreeItem<T>> selected = new ArrayList<>(0);
EventHandler<TreeModificationEvent<T>> preAdd = event -> {
event.consume();
selected.addAll(tree.getSelectionModel().getSelectedItems());
tree.getSelectionModel().clearSelection();
};
EventHandler<TreeModificationEvent<T>> postAdd = event -> {
event.consume();
selected.forEach(tree.getSelectionModel()::select);
selected.clear();
};
ChangeListener<TreeItem<T>> rootListener = (observable, oldValue, newValue) -> {
if (oldValue != null) {
oldValue.removeEventHandler(LoadingTreeItem.preAddLoadedChildrenEvent(), preAdd);
oldValue.removeEventHandler(LoadingTreeItem.postAddLoadedChildrenEvent(), postAdd);
}
if (newValue != null) {
newValue.addEventHandler(LoadingTreeItem.preAddLoadedChildrenEvent(), preAdd);
newValue.addEventHandler(LoadingTreeItem.postAddLoadedChildrenEvent(), postAdd);
}
};
rootListener.changed(tree.rootProperty(), null, tree.getRoot());
tree.rootProperty().addListener(rootListener);
}
private TreeViewUtils() {}
}
在实现时,安装解决方法的实用程序方法与您在 TreeView 中使用 LoadingTreeItem 相关联。我想不出一个好方法来使解决方案足够通用以适用于任意 TreeView;为此,我认为创建自定义 FocusModel 是必要的。
可能有更好的方法来实现 LazyTreeCell,方法是使用一个类来包装真实数据——就像您对 Item 所做的一样。然后你可以有一个实际的占位符 Item 实例,它告诉 TreeCell 它是一个占位符,而不是依赖于父级 TreeItem 是什么类型。事实上,我的实现可能很脆弱。
关于TreeView 上 TreeItems 的 JavaFX 优化异步延迟加载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52360195/
exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby中使用两个参数异步运行exe吗?我已经尝试过ruby命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何rubygems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除
鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
我一直致力于让我们的Rails2.3.8应用程序在JRuby下正确运行。一切正常,直到我启用config.threadsafe!以实现JRuby提供的并发性。这导致lib/中的模块和类不再自动加载。使用config.threadsafe!启用:$rubyscript/runner-eproduction'pSim::Sim200Provisioner'/Users/amchale/.rvm/gems/jruby-1.5.1@web-services/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:105:in`co
我们目前正在为ROR3.2开发自定义cms引擎。在这个过程中,我们希望成为我们的rails应用程序中的一等公民的几个类类型起源,这意味着它们应该驻留在应用程序的app文件夹下,它是插件。目前我们有以下类型:数据源数据类型查看我在app文件夹下创建了多个目录来保存这些:应用/数据源应用/数据类型应用/View更多类型将随之而来,我有点担心应用程序文件夹被这么多目录污染。因此,我想将它们移动到一个子目录/模块中,该子目录/模块包含cms定义的所有类型。所有类都应位于MyCms命名空间内,目录布局应如下所示:应用程序/my_cms/data_source应用程序/my_cms/data_ty
在我做的一些网络开发中,我有多个操作开始,比如对外部API的GET请求,我希望它们同时开始,因为一个不依赖另一个的结果。我希望事情能够在后台运行。我找到了concurrent-rubylibrary这似乎运作良好。通过将其混合到您创建的类中,该类的方法具有在后台线程上运行的异步版本。这导致我编写如下代码,其中FirstAsyncWorker和SecondAsyncWorker是我编写的类,我在其中混合了Concurrent::Async模块,并编写了一个名为“work”的方法来发送HTTP请求:defindexop1_result=FirstAsyncWorker.new.async.
是否可以在所有delayed_job任务之前运行一个方法?基本上,我们试图确保每个运行delayed_job的服务器都有我们代码的最新实例,所以我们想运行一个方法来在每个作业运行之前检查它。(我们已经有了“check”方法并在别处使用它。问题只是关于如何从delayed_job中调用它。) 最佳答案 现在有一种官方方法可以通过插件来做到这一点。这篇博文通过示例清楚地描述了如何执行此操作http://www.salsify.com/blog/delayed-jobs-callbacks-and-hooks-in-rails(本文中描述
如何只加载map边界内的标记gmaps4rails?当然,在平移和/或缩放后加载新的。与此直接相关的是,如何获取map的当前边界和缩放级别? 最佳答案 我是这样做的,我只在用户完成平移或缩放后替换标记,如果您需要不同的行为,请使用不同的事件监听器:在你看来(index.html.erb):{"zoom"=>15,"auto_adjust"=>false,"detect_location"=>true,"center_on_user"=>true}},false,true)%>在View的底部添加:functiongmaps4rail
我需要做这样的事情classUser'User',:foreign_key=>'abuser_id'belongs_to:gameendclassGame['JOINabuse_reportsONusers.id=abuse_reports.abuser_id','JOINgamesONgames.id=abuse_reports.game_id'],:group=>'users.id',:select=>'users.*,count(distinctgames.id)ASgame_count,count(abuse_reports.id)asabuse_report_count',:
我指的是pubrailscasttutorial并已正确执行所有步骤,但在运行最后一个命令时,即rackupprivate_pub.ru-sthin-Eproduction为了架设faye服务器,我收到以下错误:/usr/lib/ruby/1.9.1/rubygems/custom_require.rb:36:in`require':cannotloadsuchfile--thin(LoadError)from/usr/lib/ruby/1.9.1/rubygems/custom_require.rb:36:in`require'from/var/lib/gems/1.9.1/gems