I'm writing an app in JavaFX that needs to occasionally load large CSV files around 1,000,000 lines long (or possibly more).
When a user clicks a button to start loading the file, a Service is started to load the contents, with a progress/cancel dialog showing in the meantime. The call()
method in the Service is basically a while loop that loads another line from the CSV file on each iteration.
The problem is that when I start the service, the progress bar (indeterminate style) becomes jerky. Dragging the dialog is also jerky and laggy.
I wasn't having good luck searching on the web for a solution, but the closest I found was to put a Thread.sleep()
in the loop, giving other things like GC a chance to catch up.
This solution seemed to reduce/remove the stuttering, but it would add a lot of time to loading the data. I am also guessing that the exact time to sleep would vary between different processors.
Is there any way to dynamically figure out how long/often to sleep for? Or call some method that would block for just long enough to keep the GUI responsive?
The code for my service:
public class CSVLoadingService extends Service<List<ObservableList<DoubleProperty>>> {
private ObjectProperty<File> srcFile = new SimpleObjectProperty<>();
private IntegerProperty startIndex = new SimpleIntegerProperty(0);
private ObjectProperty<Character> csvDelimeter = new SimpleObjectProperty(CSVParser.DEFAULT_SEPARATOR);
private DoubleProperty invalidCSVReplacement = new SimpleDoubleProperty(0);
private ObjectProperty<Dialog> dialog = new SimpleObjectProperty<>(null);
@Override
protected Task<List<ObservableList<DoubleProperty>>> createTask() {
return new Task<List<ObservableList<DoubleProperty>>>() {
final ObjectProperty<File> _srcFile = srcFile;
final IntegerProperty _startIndex = startIndex;
final ObjectProperty<Character> _csvDelimeter = csvDelimeter;
final DoubleProperty _invalidCSVReplacement = invalidCSVReplacement;
@Override
protected ObservableList<ObservableList<DoubleProperty>> call() throws Exception {
if (_startIndex.getValue() < 0)
throw new IllegalArgumentException("Start index can't be negative.");
if (_srcFile.getValue() == null)
throw new IllegalStateException("File can't be null.");
final ObservableList<ObservableList<DoubleProperty>> result = FXCollections.observableArrayList();
// Read the data from the CSV file.
try (final CSVReader reader = new CSVReader(new BufferedReader(new FileReader(_srcFile.getValue())),
_csvDelimeter.getValue(),
CSVParser.DEFAULT_QUOTE_CHARACTER,
_startIndex.getValue()))
{
// Read first line.
String[] csvLine = reader.readNext();
// If there is actually data, then read the rest of it.
if (csvLine == null || csvLine.length == 0) {
result.clear();
} else {
// Create columns.
for (String value : csvLine) {
result.add(FXCollections.observableArrayList());
}
// Parse the CSV reads and add them to the columns.
int iteration = 0;
do {
int i = 0;
for (String value : csvLine) {
// Convert the string to a number and add it to the column.
try {
result.get(i).add(new SimpleDoubleProperty(Double.parseDouble(value)));
} catch (NumberFormatException|NullPointerException e) {
result.get(i).add(_invalidCSVReplacement);
}
}
iteration++;
} while (!isCancelled() && null != (csvLine = reader.readNext()));
}
}
return result;
}
};
}
@Override
protected void succeeded() {
super.succeeded();
if (dialog.getValue() != null) {
dialog.getValue().close();
}
}
@Override
protected void failed() {
super.failed();
if (dialog.getValue() != null) {
dialog.getValue().close();
}
}