如何等待一系列嵌套的 CompletableFutures 完成?
How to wait until all of a series of nested CompletableFutures are done?
我有一个增强现实应用程序,其中 ARObject
是一个 POJO:
class ARObject {
CompletableFuture<Texture> texture;
CompletableFuture<Material> material;
ModelRenderable renderable;
void setTexture(CompletableFuture<Texture> texture) {
this.texture = texture;
}
CompletableFuture<Texture> getTexture() {
return texture;
}
void setMaterial(CompletableFuture<Material> material) {
this.material = material;
}
CompletableFuture<Material> getMaterial() {
return material;
}
}
场景是实时合成的。在此过程中,需要构建 Texture
个对象,然后基于 Texture
个对象构建 Material
个对象。一旦 Material
准备就绪,那么 ShapeFactory
可用于生成实际的 AR 对象(作为 Renderable
的形式)。这意味着对于每个 AR 对象,构建逻辑包含两个相互嵌套的 CompletableFuture
:
for (ARObject arObject : arObjects) {
Texture.Builder textureBuilder = Texture.builder();
textureBuilder.setSource(context, arObject.resourceId);
CompletableFuture<Texture> texturePromise = textureBuilder.build(); // Future #1
arObject.setTexture(texturePromise);
texturePromise.thenAccept(texture -> {
CompletableFuture<Material> materialPromise =
MaterialFactory.makeOpaqueWithTexture(context, texture); // Future #2
arObject.setMaterial(materialPromise);
});
}
完成场景构建的一种方法是等到所有 CompletableFuture
都完成,然后才能进行 ShapeFactory
步骤。
我尝试在 Future
上使用 .get()
,但这不仅会完全破坏异步调用提供的并行性,而且还会锁定应用程序,因为我认为它导致在 UI 线程上等待。
Arrays.stream(arObjectList).forEach(a -> {
try {
a.getTexture().get();
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, "Texture CompletableFuture waiting problem " + e.toString());
}
});
Arrays.stream(arObjectList).forEach(a -> {
try {
a.getMaterial().get();
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, "Material CompletableFuture waiting problem " + e.toString());
}
});
我将构建过程分解为几个在调用链中相互调用的函数。链条如下:
populateScene
afterTexturesLoaded
afterTexturesSet
waitForMaterials
afterMaterialsLoaded
private void afterMaterialsLoaded() {
// Step 3: composing scene objects
// Get a handler that can be used to post to the main thread
Handler mainHandler = new Handler(context.getMainLooper());
for (ARObject arObject : arObjectList) {
try {
Material textureMaterial = arObject.getMaterial().get();
RunnableShapeBuilder shapeBuilder = new RunnableShapeBuilder(arObject, this, textureMaterial);
mainHandler.post(shapeBuilder);
}
catch (ExecutionException | InterruptedException e) {
Log.e(TAG, "Scene populating exception " + e.toString());
}
}
}
private Long waitForMaterials() {
while (!Stream.of(arObjectList).allMatch(arObject -> arObject.getMaterial() != null)) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
return 0L;
}
private void afterTexturesSet() {
boolean materialsDone = Stream.of(arObjectList).allMatch(arObject -> arObject.getMaterial() != null && arObject.getMaterial().isDone());
// If any of the materials are not loaded, then recurse until all are loaded.
if (!materialsDone) {
CompletableFuture<Texture>[] materialPromises =
Stream.of(arObjectList).map(ARObject::getMaterial).toArray(CompletableFuture[]::new);
CompletableFuture.allOf(materialPromises)
.thenAccept((Void aVoid) -> afterMaterialsLoaded())
.exceptionally(
throwable -> {
Log.e(TAG, "Exception building scene", throwable);
return null;
});
} else {
afterMaterialsLoaded();
}
}
private void afterTexturesLoaded() {
// Step 2: material loading
CompletableFuture materialsSetPromise = CompletableFuture.supplyAsync(this::waitForMaterials);
CompletableFuture.allOf(materialsSetPromise)
.thenAccept((Void aVoid) -> afterTexturesSet())
.exceptionally(
throwable -> {
Log.e(TAG, "Exception building scene", throwable);
return null;
});
}
/**
* Called when the AugmentedImage is detected and should be rendered. A Sceneform node tree is
* created based on an Anchor created from the image.
*/
@SuppressWarnings({"AndroidApiChecker", "FutureReturnValueIgnored"})
void populateScene() {
// Step 1: texture loading
boolean texturesDone = Stream.of(arObjectList).allMatch(arObject -> arObject.getTexture() != null && arObject.getTexture().isDone());
// If any of the textures are not loaded, then recurse until all are loaded.
if (!texturesDone) {
CompletableFuture<Texture>[] texturePromises =
Stream.of(arObjectList).map(ARObject::getTexture).toArray(CompletableFuture[]::new);
CompletableFuture.allOf(texturePromises)
.thenAccept((Void aVoid) -> afterTexturesLoaded())
.exceptionally(
throwable -> {
Log.e(TAG, "Exception building scene", throwable);
return null;
});
} else {
afterTexturesLoaded();
}
}
这有几个问题。第一:它仍然在某种程度上破坏了异步的本质。在理想情况下,相应的 material 纹理对将从其他对中独立加载和生成。在这个最新版本中,执行流程中有很多交汇点,不符合理想的独立场景。第二:我什至无法避免 waitForMaterials
我有丑陋 Thread.sleep()
的步骤。第三:代码总体上仍然失败,因为在最后一步从加载的纹理和 materials 最终构建形状时,我遇到了错误 java.lang.IllegalStateException: Must be called from the UI thread.
。因此,我进行了另一种扭曲:RunnableShapeBuilder
。如此也无一例外,但现场还是什么都没有,代码就更复杂了。
class RunnableShapeBuilder implements Runnable {
ARObject arObject;
AnchorNode parentNode;
Material textureMaterial;
RunnableShapeBuilder(ARObject arObject, AnchorNode parentNode, Material textureMaterial) {
this.arObject = arObject;
this.parentNode = parentNode;
this.textureMaterial = textureMaterial;
}
@Override
public void run() {
arObject.renderable = ShapeFactory.makeCube(
new Vector3(0.5f, 1, 0.01f),
new Vector3(0.0f, 0.0f, 0.0f),
textureMaterial
);
...
}
}
答案是:我不必等待这些嵌套的 CompletableFuture
。虽然我的场景变得更加复杂,但我认为我必须等到 3D Material 和纹理的构建完成。问题出在题外话的地方:尽管我设置了 AR 对象的锚点。 AR 对象的锚点来自命中测试,我需要将锚点的父级设置为 AR 场景。这最后一步在一些重构过程中丢失了,如果发生这种情况没有任何警告,AR 场景中没有任何显示。
关注问题本身:我强烈不建议尝试等待任何问题,因为这只会导致痛苦和痛苦。寻求不同的解决方案。
我有一个增强现实应用程序,其中 ARObject
是一个 POJO:
class ARObject {
CompletableFuture<Texture> texture;
CompletableFuture<Material> material;
ModelRenderable renderable;
void setTexture(CompletableFuture<Texture> texture) {
this.texture = texture;
}
CompletableFuture<Texture> getTexture() {
return texture;
}
void setMaterial(CompletableFuture<Material> material) {
this.material = material;
}
CompletableFuture<Material> getMaterial() {
return material;
}
}
场景是实时合成的。在此过程中,需要构建 Texture
个对象,然后基于 Texture
个对象构建 Material
个对象。一旦 Material
准备就绪,那么 ShapeFactory
可用于生成实际的 AR 对象(作为 Renderable
的形式)。这意味着对于每个 AR 对象,构建逻辑包含两个相互嵌套的 CompletableFuture
:
for (ARObject arObject : arObjects) {
Texture.Builder textureBuilder = Texture.builder();
textureBuilder.setSource(context, arObject.resourceId);
CompletableFuture<Texture> texturePromise = textureBuilder.build(); // Future #1
arObject.setTexture(texturePromise);
texturePromise.thenAccept(texture -> {
CompletableFuture<Material> materialPromise =
MaterialFactory.makeOpaqueWithTexture(context, texture); // Future #2
arObject.setMaterial(materialPromise);
});
}
完成场景构建的一种方法是等到所有 CompletableFuture
都完成,然后才能进行 ShapeFactory
步骤。
我尝试在 Future
上使用 .get()
,但这不仅会完全破坏异步调用提供的并行性,而且还会锁定应用程序,因为我认为它导致在 UI 线程上等待。
Arrays.stream(arObjectList).forEach(a -> {
try {
a.getTexture().get();
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, "Texture CompletableFuture waiting problem " + e.toString());
}
});
Arrays.stream(arObjectList).forEach(a -> {
try {
a.getMaterial().get();
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, "Material CompletableFuture waiting problem " + e.toString());
}
});
我将构建过程分解为几个在调用链中相互调用的函数。链条如下:
populateScene
afterTexturesLoaded
afterTexturesSet
waitForMaterials
afterMaterialsLoaded
private void afterMaterialsLoaded() {
// Step 3: composing scene objects
// Get a handler that can be used to post to the main thread
Handler mainHandler = new Handler(context.getMainLooper());
for (ARObject arObject : arObjectList) {
try {
Material textureMaterial = arObject.getMaterial().get();
RunnableShapeBuilder shapeBuilder = new RunnableShapeBuilder(arObject, this, textureMaterial);
mainHandler.post(shapeBuilder);
}
catch (ExecutionException | InterruptedException e) {
Log.e(TAG, "Scene populating exception " + e.toString());
}
}
}
private Long waitForMaterials() {
while (!Stream.of(arObjectList).allMatch(arObject -> arObject.getMaterial() != null)) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
return 0L;
}
private void afterTexturesSet() {
boolean materialsDone = Stream.of(arObjectList).allMatch(arObject -> arObject.getMaterial() != null && arObject.getMaterial().isDone());
// If any of the materials are not loaded, then recurse until all are loaded.
if (!materialsDone) {
CompletableFuture<Texture>[] materialPromises =
Stream.of(arObjectList).map(ARObject::getMaterial).toArray(CompletableFuture[]::new);
CompletableFuture.allOf(materialPromises)
.thenAccept((Void aVoid) -> afterMaterialsLoaded())
.exceptionally(
throwable -> {
Log.e(TAG, "Exception building scene", throwable);
return null;
});
} else {
afterMaterialsLoaded();
}
}
private void afterTexturesLoaded() {
// Step 2: material loading
CompletableFuture materialsSetPromise = CompletableFuture.supplyAsync(this::waitForMaterials);
CompletableFuture.allOf(materialsSetPromise)
.thenAccept((Void aVoid) -> afterTexturesSet())
.exceptionally(
throwable -> {
Log.e(TAG, "Exception building scene", throwable);
return null;
});
}
/**
* Called when the AugmentedImage is detected and should be rendered. A Sceneform node tree is
* created based on an Anchor created from the image.
*/
@SuppressWarnings({"AndroidApiChecker", "FutureReturnValueIgnored"})
void populateScene() {
// Step 1: texture loading
boolean texturesDone = Stream.of(arObjectList).allMatch(arObject -> arObject.getTexture() != null && arObject.getTexture().isDone());
// If any of the textures are not loaded, then recurse until all are loaded.
if (!texturesDone) {
CompletableFuture<Texture>[] texturePromises =
Stream.of(arObjectList).map(ARObject::getTexture).toArray(CompletableFuture[]::new);
CompletableFuture.allOf(texturePromises)
.thenAccept((Void aVoid) -> afterTexturesLoaded())
.exceptionally(
throwable -> {
Log.e(TAG, "Exception building scene", throwable);
return null;
});
} else {
afterTexturesLoaded();
}
}
这有几个问题。第一:它仍然在某种程度上破坏了异步的本质。在理想情况下,相应的 material 纹理对将从其他对中独立加载和生成。在这个最新版本中,执行流程中有很多交汇点,不符合理想的独立场景。第二:我什至无法避免 waitForMaterials
我有丑陋 Thread.sleep()
的步骤。第三:代码总体上仍然失败,因为在最后一步从加载的纹理和 materials 最终构建形状时,我遇到了错误 java.lang.IllegalStateException: Must be called from the UI thread.
。因此,我进行了另一种扭曲:RunnableShapeBuilder
。如此也无一例外,但现场还是什么都没有,代码就更复杂了。
class RunnableShapeBuilder implements Runnable {
ARObject arObject;
AnchorNode parentNode;
Material textureMaterial;
RunnableShapeBuilder(ARObject arObject, AnchorNode parentNode, Material textureMaterial) {
this.arObject = arObject;
this.parentNode = parentNode;
this.textureMaterial = textureMaterial;
}
@Override
public void run() {
arObject.renderable = ShapeFactory.makeCube(
new Vector3(0.5f, 1, 0.01f),
new Vector3(0.0f, 0.0f, 0.0f),
textureMaterial
);
...
}
}
答案是:我不必等待这些嵌套的 CompletableFuture
。虽然我的场景变得更加复杂,但我认为我必须等到 3D Material 和纹理的构建完成。问题出在题外话的地方:尽管我设置了 AR 对象的锚点。 AR 对象的锚点来自命中测试,我需要将锚点的父级设置为 AR 场景。这最后一步在一些重构过程中丢失了,如果发生这种情况没有任何警告,AR 场景中没有任何显示。
关注问题本身:我强烈不建议尝试等待任何问题,因为这只会导致痛苦和痛苦。寻求不同的解决方案。