Подтвердить что ты не робот

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();
} 

в блоке ответа, но это кажется очень утомительным.

4b9b3361

Ответ 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.