我正在尝试实现一个线程安全的解决方案,以保持对已完成的成功任务的计数,这些任务最终将绑定到UI上显示的标签。但是,当我使用下面的AtomicInteger时,它会在任务开始运行时锁定我的UI,但是,如果我删除所有AtomicInteger引用,一切都会正常进行。有没有一种可以实现此目标的非阻塞线程安全方法?
public void handleSomeButtonClick(){
if(!dataModel.getSomeList().isEmpty()) {
boolean unlimited = false;
int count = 0;
AtomicInteger successCount = new AtomicInteger(0);
if(countSelector.getValue().equalsIgnoreCase("Unlimited"))
unlimited = true;
else
count = Integer.parseInt(countSelector.getValue());
while(unlimited || successCount.get() < count) {
Task task = getSomeTask();
taskExecutor.submit(task);
task.setOnSucceeded(event -> {
if (task.getValue())
log.info("Successfully Completed Task | Total Count: " + successCount.incrementAndGet());
else
log.error("Failed task");
});
}
}
}
答案 0 :(得分:1)
您的循环等待一定数量的任务完成。甚至可能是一个无限循环。
这不是一个好主意:
count
可以是3,但是由于您仅计划循环中的任务,因此在第一个任务完成之前可以创建和计划1000个或更多任务。此外,如果您使用onSucceeded
/ onFailed
,则不需要使用AtomicInteger
或任何类似类型的同步,因为这些处理程序都在JavaFX应用程序线程上运行。 / p>
您的代码可以这样重写:
private int successCount;
private void scheduleTask(final boolean unlimited) {
Task task = getSomeTask();
task.setOnSucceeded(event -> {
// cannot get a Boolean from a raw task, so I assume the task is successfull iff no exception happens
successCount++;
log.info("Successfully Completed Task | Total Count: " + successCount);
if (unlimited) {
// submit new task, if the number of tasks is unlimited
scheduleTask(true);
}
});
// submit new task on failure
task.setOnFailed(evt -> scheduleTask(unlimited));
taskExecutor.submit(task);
}
public void handleSomeButtonClick() {
if(!dataModel.getSomeList().isEmpty()) {
successCount = 0;
final boolean unlimited;
final int count;
if(countSelector.getValue().equalsIgnoreCase("Unlimited")) {
unlimited = true;
count = 4; // set limit of number of tasks submitted to the executor at the same time
} else {
count = Integer.parseInt(countSelector.getValue());
unlimited = false;
}
for (int i = 0; i < count; i++) {
scheduleTask(unlimited);
}
}
}
注意::此代码冒着在完成之前的任务之前多次单击handleButtonClick
的风险。您应该避免在完成旧任务之前安排新任务,或者使用包含int
的某种引用类型来代替计数,在handleSomeButtonClick
中创建该对象并将该对象传递给scheduleTask
。
答案 1 :(得分:0)
您的问题是future.get()块并等待结果。 如果使用Vavr库,这将很简单。 因为它可以将代码附加到其将来,成功或失败后会自动运行。 因此,您不必等待。 这是一个使用Vavr未来的示例。
CheckedFunction0<String> thisIsATask = () -> {
if ( /*do something*/ ){
throw new Exception("Hey");
}
return "ABC";
};
List<Future<String>> futureList = new ArrayList<>();
for (int x = 0; x < 10; x++) {
futureList.add(Future.of(getExecutorService(), thisIsATask));
}
futureList.forEach((task) -> {
// This will run if success
task.onSuccess(s -> s.equals("ABC") ? Platform.runLater(()->UpdateCounter()) : wtf());
// Your get the exception if it is fail;
task.onFailure(e -> e.printStackTrace());
// task.onComplete() will run on any case when complete
});
这不是阻塞,任务完成或捕获到异常时,onSucess onFailure或onComplete上的代码将运行。
注意:Future.of将使用您传入的executorService在新线程上运行每个任务,一旦完成任务,您在onSuccess上提供的代码将继续在该线程上运行,因此如果您调用javafx,请记住平台。 runLater()
如果您想在所有任务完成后运行某些程序,那么
// the code at onComplete will run when tasks all done
Future<Seq<String>> all = Future.sequence(futureList);
all.onComplete((i) -> this.btnXYZ.setDisable(false));