简化 Task (#5365)

This commit is contained in:
Glavo
2026-02-01 19:10:01 +08:00
committed by GitHub
parent c45cd74b67
commit c1d0bb973a
4 changed files with 115 additions and 98 deletions

View File

@@ -326,8 +326,7 @@ public final class TaskListPane extends StackPane {
if (prevStageNodeRef != null && (prevStageNode = prevStageNodeRef.get()) != null)
prevStageNode.status.removeListener(statusChangeListener);
if (item instanceof ProgressListNode) {
var progressListNode = (ProgressListNode) item;
if (item instanceof ProgressListNode progressListNode) {
title.setText(progressListNode.title);
message.textProperty().bind(progressListNode.message);
bar.progressProperty().bind(progressListNode.progress);
@@ -336,8 +335,7 @@ public final class TaskListPane extends StackPane {
pane.setLeft(null);
pane.setRight(message);
pane.setBottom(bar);
} else if (item instanceof StageNode) {
var stageNode = (StageNode) item;
} else if (item instanceof StageNode stageNode) {
title.textProperty().bind(stageNode.title);
message.setText("");
bar.setProgress(ProgressIndicator.INDETERMINATE_PROGRESS);
@@ -493,12 +491,10 @@ public final class TaskListPane extends StackPane {
private ProgressListNode(Task<?> task) {
this.title = task.getName();
message.bind(task.messageProperty());
progress.bind(task.progressProperty());
}
public void unbind() {
message.unbind();
progress.unbind();
}

View File

@@ -92,7 +92,7 @@ public final class AsyncTaskExecutor extends TaskExecutor {
throw new IllegalStateException("Cannot cancel a not started TaskExecutor");
}
cancelled.set(true);
cancelled = true;
}
private CompletableFuture<?> executeTasksExceptionally(Task<?> parentTask, Collection<? extends Task<?>> tasks) {
@@ -101,8 +101,6 @@ public final class AsyncTaskExecutor extends TaskExecutor {
return CompletableFuture.completedFuture(null)
.thenComposeAsync(unused -> {
totTask.addAndGet(tasks.size());
if (isCancelled()) {
for (Task<?> task : tasks) task.setException(new CancellationException());
return CompletableFuture.runAsync(this::checkCancellation);
@@ -164,7 +162,7 @@ public final class AsyncTaskExecutor extends TaskExecutor {
}
task.setResult(result);
task.onDone().fireEvent(new TaskEvent(this, task, false));
task.fireDoneEvent(this, false);
taskListeners.forEach(it -> it.onFinished(task));
task.setState(Task.TaskState.SUCCEEDED);
@@ -173,14 +171,13 @@ public final class AsyncTaskExecutor extends TaskExecutor {
})
.exceptionally(throwable -> {
Throwable resolved = resolveException(throwable);
if (resolved instanceof Exception) {
Exception e = (Exception) resolved;
if (resolved instanceof Exception e) {
if (e instanceof InterruptedException || e instanceof CancellationException) {
task.setException(null);
if (task.getSignificance().shouldLog()) {
LOG.trace("Task aborted: " + task.getName());
}
task.onDone().fireEvent(new TaskEvent(this, task, true));
task.fireDoneEvent(this, true);
taskListeners.forEach(it -> it.onFailed(task, e));
} else {
task.setException(e);
@@ -188,7 +185,7 @@ public final class AsyncTaskExecutor extends TaskExecutor {
if (task.getSignificance().shouldLog()) {
LOG.trace("Task failed: " + task.getName(), e);
}
task.onDone().fireEvent(new TaskEvent(this, task, true));
task.fireDoneEvent(this, true);
taskListeners.forEach(it -> it.onFailed(task, e));
}
@@ -278,7 +275,7 @@ public final class AsyncTaskExecutor extends TaskExecutor {
LOG.trace("Task finished: " + task.getName());
}
task.onDone().fireEvent(new TaskEvent(this, task, false));
task.fireDoneEvent(this, false);
taskListeners.forEach(it -> it.onFinished(task));
task.setState(Task.TaskState.SUCCEEDED);
@@ -300,7 +297,7 @@ public final class AsyncTaskExecutor extends TaskExecutor {
LOG.trace("Task failed: " + task.getName(), e);
}
}
task.onDone().fireEvent(new TaskEvent(this, task, true));
task.fireDoneEvent(this, true);
taskListeners.forEach(it -> it.onFailed(task, e));
task.setState(Task.TaskState.FAILED);
@@ -311,8 +308,8 @@ public final class AsyncTaskExecutor extends TaskExecutor {
}
private <T> CompletableFuture<T> executeTask(Task<?> parentTask, Task<T> task) {
if (task instanceof CompletableFutureTask<?>) {
return executeCompletableFutureTask(parentTask, (CompletableFutureTask<T>) task);
if (task instanceof CompletableFutureTask<T> completableFutureTask) {
return executeCompletableFutureTask(parentTask, completableFutureTask);
} else {
return executeNormalTask(parentTask, task);
}

View File

@@ -18,18 +18,18 @@
package org.jackhuang.hmcl.task;
import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleDoubleProperty;
import org.jackhuang.hmcl.event.EventManager;
import org.jackhuang.hmcl.util.InvocationDispatcher;
import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
import org.jetbrains.annotations.Nullable;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
@@ -47,8 +47,6 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
*/
public abstract class Task<T> {
private final EventManager<TaskEvent> onDone = new EventManager<>();
/**
* True if not logging when executing this task.
*/
@@ -64,9 +62,9 @@ public abstract class Task<T> {
}
// cancel
private Supplier<Boolean> cancelled;
private BooleanSupplier cancelled;
final void setCancelled(Supplier<Boolean> cancelled) {
final void setCancelled(BooleanSupplier cancelled) {
this.cancelled = cancelled;
}
@@ -76,7 +74,7 @@ public abstract class Task<T> {
return true;
}
return cancelled != null ? cancelled.get() : false;
return cancelled != null && cancelled.getAsBoolean();
}
// stage
@@ -92,6 +90,7 @@ public abstract class Task<T> {
/**
* You must initialize stage in constructor.
*
* @param stage the stage
*/
protected final void setStage(String stage) {
@@ -211,10 +210,10 @@ public abstract class Task<T> {
}
// name
private String name = getClass().getName();
private String name;
public String getName() {
return name;
return name != null ? name : getClass().getName();
}
public Task<T> setName(String name) {
@@ -236,7 +235,7 @@ public abstract class Task<T> {
/**
* Returns the result of this task.
*
* <p>
* The result will be generated only if the execution is completed.
*/
public T getResult() {
@@ -270,7 +269,8 @@ public abstract class Task<T> {
* @throws InterruptedException if current thread is interrupted
* @see Thread#isInterrupted()
*/
public void preExecute() throws Exception {}
public void preExecute() throws Exception {
}
/**
* @throws InterruptedException if current thread is interrupted
@@ -284,7 +284,7 @@ public abstract class Task<T> {
/**
* This method will be called after dependency tasks terminated all together.
*
* <p>
* You can check whether dependencies succeed in this method by calling
* {@link Task#isDependenciesSucceeded()} no matter when
* {@link Task#isRelyingOnDependencies()} returns true or false.
@@ -293,7 +293,8 @@ public abstract class Task<T> {
* @see Thread#isInterrupted()
* @see Task#isDependenciesSucceeded()
*/
public void postExecute() throws Exception {}
public void postExecute() throws Exception {
}
/**
* The collection of sub-tasks that should execute **before** this task running.
@@ -310,44 +311,74 @@ public abstract class Task<T> {
return Collections.emptySet();
}
private volatile EventManager<TaskEvent> onDone;
public EventManager<TaskEvent> onDone() {
EventManager<TaskEvent> onDone = this.onDone;
if (onDone == null) {
synchronized (this) {
onDone = this.onDone;
if (onDone == null) {
this.onDone = onDone = new EventManager<>();
}
}
}
return onDone;
}
protected long getProgressInterval() {
return 1000L;
void fireDoneEvent(Object source, boolean failed) {
EventManager<TaskEvent> onDone = this.onDone;
if (onDone != null)
onDone.fireEvent(new TaskEvent(source, this, failed));
}
private long lastTime = Long.MIN_VALUE;
private final ReadOnlyDoubleWrapper progress = new ReadOnlyDoubleWrapper(this, "progress", -1);
private final InvocationDispatcher<Double> progressUpdate = InvocationDispatcher.runOn(Platform::runLater, progress::set);
private final DoubleProperty progress = new SimpleDoubleProperty(this, "progress", -1);
public ReadOnlyDoubleProperty progressProperty() {
return progress.getReadOnlyProperty();
return progress;
}
private long lastUpdateProgressTime = 0L;
protected void updateProgress(long progress, long total) {
updateProgress(1.0 * progress / total);
}
protected void updateProgress(double progress) {
if (progress < 0 || progress > 1.0)
throw new IllegalArgumentException("Progress is must between 0 and 1.");
if (progress < 0 || progress > 1.0 || Double.isNaN(progress))
throw new IllegalArgumentException("Progress must be between 0 and 1.");
long now = System.currentTimeMillis();
if (lastTime == Long.MIN_VALUE || now - lastTime >= getProgressInterval()) {
if (progress == 1.0 || now - lastUpdateProgressTime >= 1000L) {
updateProgressImmediately(progress);
lastTime = now;
lastUpdateProgressTime = now;
}
}
//region Helpers for updateProgressImmediately
@SuppressWarnings("FieldMayBeFinal")
private volatile double pendingProgress = -1.0;
/// @see Task#pendingProgress
private static final VarHandle PENDING_PROGRESS_HANDLE;
static {
try {
PENDING_PROGRESS_HANDLE = MethodHandles.lookup()
.findVarHandle(Task.class, "pendingProgress", double.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}
//endregion updateProgressImmediately
protected void updateProgressImmediately(double progress) {
progressUpdate.accept(progress);
// assert progress >= 0 && progress <= 1.0;
if ((double) PENDING_PROGRESS_HANDLE.getAndSet(this, progress) == -1.0) {
Platform.runLater(() -> this.progress.set((double) PENDING_PROGRESS_HANDLE.getAndSet(this, -1.0)));
}
private final ReadOnlyStringWrapper message = new ReadOnlyStringWrapper(this, "message", null);
public final ReadOnlyStringProperty messageProperty() {
return message.getReadOnlyProperty();
}
public final T run() throws Exception {
@@ -359,16 +390,14 @@ public abstract class Task<T> {
execute();
for (Task<?> task : getDependencies())
doSubTask(task);
onDone.fireEvent(new TaskEvent(this, this, false));
fireDoneEvent(this, false);
return getResult();
}
private void doSubTask(Task<?> task) throws Exception {
message.bind(task.message);
progress.bind(task.progress);
task.run();
message.unbind();
progress.unbind();
}
@@ -994,10 +1023,12 @@ public abstract class Task<T> {
FAILED
}
@FunctionalInterface
public interface FinalizedCallback {
void execute(Exception exception) throws Exception;
}
@FunctionalInterface
public interface FinalizedCallbackWithResult<T> {
void execute(T result, Exception exception) throws Exception;
}

View File

@@ -20,20 +20,17 @@ package org.jackhuang.hmcl.task;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class TaskExecutor {
protected final Task<?> firstTask;
protected final List<TaskListener> taskListeners = new ArrayList<>();
protected final AtomicInteger totTask = new AtomicInteger(0);
protected final AtomicBoolean cancelled = new AtomicBoolean(false);
protected final List<TaskListener> taskListeners = new ArrayList<>(0);
protected volatile boolean cancelled = false;
protected Exception exception;
private final List<String> stages;
public TaskExecutor(Task<?> task) {
this.firstTask = task;
this.stages = task instanceof Task.StagesHintTask ? ((Task<?>.StagesHintTask) task).getStages() : Collections.emptyList();
this.stages = task instanceof Task<?>.StagesHintTask hintTask ? hintTask.getStages() : Collections.emptyList();
}
public void addTaskListener(TaskListener taskListener) {
@@ -59,11 +56,7 @@ public abstract class TaskExecutor {
public abstract void cancel();
public boolean isCancelled() {
return cancelled.get();
}
public int getTaskCount() {
return totTask.get();
return cancelled;
}
public List<String> getStages() {