如何使用CompletableFuture等待所有OKHttp异步网络请求全部完成
问题根因
你当前的实现逻辑存在两个核心问题:
CompletableFuture.runAsync会在传入的Runnable执行完成后就标记Future为完成状态,而你写的getResponse方法内部调用OkHttp的enqueue是异步非阻塞方法,getResponse把请求提交给OkHttp之后就直接返回了,不会等待网络请求的回调触发,所以你加到列表里的所有Future会在所有请求提交完成后就全部进入完成状态,根本不会等响应返回。- 原逻辑没有做响应结果透传,后续也没法收集所有响应列表。
代码修改实现
第一步:改造getResponse方法,改为返回CompletableFuture
把getResponse的返回值从void改成CompletableFuture<String>,在OkHttp的回调里手动完成Future:
private CompletableFuture<String> getResponse(final Context context, final String id) { // 先创建一个未完成的CompletableFuture CompletableFuture<String> future = new CompletableFuture<>(); Log.d("api_log", "id is: " + id); final String url = context.getString(R.string.myurl) + "/" + id; OkHttpClient client = new OkHttpClient().newBuilder() .build(); Request request = new Request.Builder() .url(url) .method("GET", null) .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); // 请求失败标记Future完成,可根据需求选择传异常或者null future.completeExceptionally(e); } @Override public void onResponse(Call call, final Response response) throws IOException { try { if (!response.isSuccessful()) { // 响应异常标记完成 future.complete(null); return; } final String strResponse = response.body().string(); Log.d("api_log", "response: " + strResponse); // 把响应结果设置到Future里,标记为完成 future.complete(strResponse); } catch (Exception e) { future.completeExceptionally(e); } finally { response.close(); } } }); // 直接返回未完成的Future,等回调触发时才会进入完成状态 return future; }
第二步:调整调用逻辑,收集所有Future
不需要额外用runAsync包裹,直接把getResponse返回的Future加到列表里即可,最后通过allOf等待所有Future完成后,还可以统一收集所有响应结果:
Log.d("api_log", "Started doing things"); List<CompletableFuture<String>> futures = new ArrayList<>(); for (int i = 0; i < mylist.size(); i++) { try { // 直接添加getResponse返回的Future即可,不需要runAsync包裹 futures.add(getResponse(context, mylist.get(i).id)); } catch (Exception e) { e.printStackTrace(); } } CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) .thenRunAsync(() -> { Log.d("api_log", "Ended doing things"); // 这里可以统一收集所有响应结果 List<String> allResponses = new ArrayList<>(); for (CompletableFuture<String> future : futures) { try { // 此时future已经全部完成,get不会阻塞 String resp = future.get(); if (resp != null) { allResponses.add(resp); } } catch (Exception e) { e.printStackTrace(); } } // 在这里处理所有响应的业务逻辑即可 });
可选优化点
- 不要每次请求都新建
OkHttpClient实例,全局复用一个单例即可,避免重复创建连接池导致的性能损耗。 - 如果需要处理请求失败的场景,可以在
completeExceptionally之后在allOf的回调里统一处理异常。
内容的提问来源于stack exchange,提问作者Erez git
相关产品推荐
相关产品推荐

