Android, как объединить задачи async вместе, как в iOS
У меня есть функция в приложении iOS, которая использует dispatch_group
для группировки нескольких запросов на отдых:
static func fetchCommentsAndTheirReplies(articleId: String, failure: ((NSError)->Void)?, success: (comments: [[String: AnyObject]], replies: [[[String: AnyObject]]], userIds: Set<String>)->Void) {
var retComments = [[String: AnyObject]]()
var retReplies = [[[String: AnyObject]]]()
var retUserIds = Set<String>()
let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
Alamofire.request(.GET, API.baseUrl + API.article.listCreateComment, parameters: [API.article.articleId: articleId]).responseJSON {
response in
dispatch_async(queue) {
guard let comments = response.result.value as? [[String: AnyObject]] else {
failure?(Helper.error())
return
}
print(comments)
retComments = comments
let group = dispatch_group_create()
for (commentIndex, comment) in comments.enumerate() {
guard let id = comment["_id"] as? String else {continue}
let relevantUserIds = helperParseRelaventUserIdsFromEntity(comment)
for userId in relevantUserIds {
retUserIds.insert(userId)
}
retReplies.append([[String: AnyObject]]())
dispatch_group_enter(group)
Alamofire.request(.GET, API.baseUrl + API.article.listCreateReply, parameters: [API.article.commentId: id]).responseJSON {
response in
dispatch_async(queue) {
if let replies = response.result.value as? [[String: AnyObject]] {
for (_, reply) in replies.enumerate() {
let relevantUserIds = helperParseRelaventUserIdsFromEntity(reply)
for userId in relevantUserIds {
retUserIds.insert(userId)
}
}
retReplies[commentIndex] = replies
}
dispatch_group_leave(group)
}
}
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
success(comments: retComments, replies: retReplies, userIds: retUserIds)
}
}
}
Как вы можете видеть из моего кода, я извлекаю все comments
под тем же article
, а затем выбираю replies
для каждого comment
. После выполнения всех запросов я вызываю свой обратный вызов success
. Этого можно добиться с помощью GCD dispatch_group
.
Теперь я переношу ту же функциональность в android.
public static void fetchCommentsAndTheirReplies(Context context, String articleId, final StringBuffer outErrorMessage, final Runnable failure, final ArrayList<JSONObject> outComments, final ArrayList<ArrayList<JSONObject>> outReplies, final HashSet<String> outUserIds, final Runnable success) {
final RequestQueue queue = Volley.newRequestQueue(context);
HashMap<String, String> commentParams = new HashMap<>();
commentParams.put(API.article.articleId, articleId);
JsonArrayRequest commentRequest = new JsonArrayRequest(Request.Method.GET, API.baseUrl + API.article.listCreateComment, new JSONObject(commentParams), new Response.Listener<JSONArray>() {
@Override
public void onResponse(JSONArray response) {
try {
for (int i = 0; i < response.length(); i++) {
JSONObject comment = response.getJSONObject(i);
outComments.add(comment);
outUserIds.addAll(helperParseRelaventUserIdsFromEntity(comment));
outReplies.add(new ArrayList<JSONObject>());
//TODO: DISPATCH_GROUP?
String id = comment.getString("_id");
HashMap<String, String> replyParams = new HashMap<>();
replyParams.put(API.article.commentId, id);
final int finalI = i;
JsonArrayRequest replyRequest = new JsonArrayRequest(Request.Method.GET, API.baseUrl + API.article.listCreateReply, new JSONObject(replyParams), new Response.Listener<JSONArray>() {
@Override
public void onResponse(JSONArray response) {
try {
for (int j = 0; j < response.length(); j++) {
JSONObject reply = response.getJSONObject(j);
outUserIds.addAll(helperParseRelaventUserIdsFromEntity(reply));
outReplies.get(finalI).add(reply);
}
} catch (JSONException ex) {}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {}
});
queue.add(replyRequest);
}
success.run();
} catch (JSONException ex) {}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
outErrorMessage.append(error.getMessage());
failure.run();
}
});
queue.add(commentRequest);
}
Обратите внимание, что я использую success
, выполняется сразу после получения всего comments
и до получения всего replies
.
Итак, как я могу сгруппировать их и задержать ответ?
Я работаю над волосатой реализацией, например
taskCount++;
if (taskCount == totalCount) {
success.run();
}
в блоке ответа, но это кажется очень утомительным.
Ответы
Ответ 1
Прямой аналог dispatch_group
отсутствует в простой Java или Android. Я могу порекомендовать несколько довольно сложных методов для создания действительно чистого и элегантного решения, если вы готовы вкладывать в него дополнительное время. К сожалению, это не будет одна или две строки кода.
-
Используйте RxJava
с распараллеливанием. RxJava
предоставляет чистый способ отправки нескольких задач, но по умолчанию он работает последовательно. См. Эту статью, чтобы она выполняла задачи одновременно.
-
Несмотря на то, что это не совсем то, что предназначено для использования, вы можете попробовать ForkJoinPool выполнить свою группу задач и получить один результат после этого.
Ответ 2
Вы можете просто сделать это с помощью этого класса, который я сделал, чтобы имитировать поведение iOS. Вызовите enter() и оставьте() так же, как вы делали в iOS, с dispatch_group_enter и dispatch_group_leave и вызывают notify() сразу после запросов, которые вы хотите сгруппировать, например dispatch_group_notify. Он также использует runnable так же, как iOS использует блоки:
public class DispatchGroup {
private int count = 0;
private Runnable runnable;
public DispatchGroup()
{
super();
count = 0;
}
public synchronized void enter(){
count++;
}
public synchronized void leave(){
count--;
notifyGroup();
}
public void notify(Runnable r) {
runnable = r;
notifyGroup();
}
private void notifyGroup(){
if (count <=0 && runnable!=null) {
runnable.run();
}
}
}
Надеюсь, что это поможет;)
Ответ 3
Ваша "волосатая" реализация не "волосатая" на всех имхо.
public void onResponse(JSONArray response) {
try {
final int[] taskFinished = {0};
final int taskTotal = response.length();
for (int i = 0; i < response.length(); i++) {
JSONObject comment = response.getJSONObject(i);
outComments.add(comment);
outUserIds.addAll(helperParseRelaventUserIdsFromEntity(comment));
outReplies.add(new ArrayList<JSONObject>());
//TODO: DISPATCH_GROUP?
String id = comment.getString("_id");
HashMap<String, String> replyParams = new HashMap<>();
replyParams.put(API.article.commentId, id);
final int finalI = i;
JsonArrayRequest replyRequest = new JsonArrayRequest(Request.Method.GET, API.baseUrl + API.article.listCreateReply, new JSONObject(replyParams), new Response.Listener<JSONArray>() {
@Override
public void onResponse(JSONArray response) {
taskFinished[0]++;
try {
for (int j = 0; j < response.length(); j++) {
JSONObject reply = response.getJSONObject(j);
outUserIds.addAll(helperParseRelaventUserIdsFromEntity(reply));
outReplies.get(finalI).add(reply);
}
} catch (JSONException ex) {}
if (taskFinished[0] == taskTotal) {
success.run();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
taskFinished[0]++;
if (taskFinished[0] == taskTotal) {
success.run();
}
}
});
queue.add(replyRequest);
}
} catch (JSONException ex) {}
}
Ответ 4
В качестве опции вы можете использовать Thread
и Thread.join()
с Handler
.
цитата из: https://docs.oracle.com/javase/tutorial/essential/concurrency/join.html
Метод join позволяет одному потоку ждать завершения другой. Если t - объект Thread, поток которого в настоящее время выполняется,
t.join(); заставляет текущий поток приостанавливать выполнение до тех пор, пока t поток завершается. Перегрузки соединения позволяют программисту указать Период ожидания. Однако, как и во сне, соединение зависит от ОС для синхронизации, поэтому вы не должны предполагать, что соединение будет ждать точно так же как вы указываете.
Как и сон, соединение реагирует на прерывание, выходя из InterruptedException.
ИЗМЕНИТЬ:
Вы также должны проверить мой диспетчер событий. Вам может понравиться.
Ответ 5
Попробуйте очередь приоритетов: https://github.com/yigit/android-priority-jobqueue
Приоритетная очередь заданий - это реализация очереди заданий написанный для Android, чтобы легко выполнять задания (задачи), которые выполняются в фон, улучшая стабильность UX и приложений.
(...)
Вы можете группировать задания для обеспечения их последовательного выполнения, если это необходимо. Для Например, предположим, что у вас есть клиент обмена сообщениями, и ваш пользователь отправил кучу сообщений, когда у их телефона нет покрытия сети. При создании эти задания SendMessageToNetwork, вы можете группировать их по цепочке Я БЫ. Благодаря такому подходу сообщения в одном чате отправят в том порядке, в котором они были установлены в очередь, в то время как сообщения между разными разговоры по-прежнему отправляются параллельно. Это позволяет легко максимизировать использование сети и обеспечить целостность данных.
Ответ 6
Не используйте Java
Дайте Kotlin попробовать, он имеет promises
task {
//some (long running) operation, or just:
1 + 1
} then {
i -> "result: $i"
} success {
msg -> println(msg)
}
https://github.com/mplatvoet/kovenant
Ответ 7
Вам не нужно, но самый простой способ решить эту проблему - использовать модификацию с rxJava вместо волейбола.
Ответ 8
Возможно, вы ищете что-то вроде Retrofit или Volley.