Руководство по использованию SynchronousQueue - PullRequest
2 голосов
/ 01 января 2012

Мне нужно выполнить серию http-запросов, каждый из которых может зависеть от предыдущего http-ответа. Я смог добиться этого, используя своего рода «дерево» AsyncTask, но с ростом дерева решений техника AsyncTask становится все более громоздкой.

Я думаю, что каким-то образом использование SynchronousQueue (или другого типа очереди) является лучшим подходом, но я не могу найти ни одного хорошего руководства или учебных пособий о том, как использовать Очередь для чего-то вроде http-запросов.

Может ли кто-нибудь дать какие-либо рекомендации или указать на хорошие уроки по использованию SynchronousQueue или предложить лучший вид очереди?

Ответы [ 2 ]

1 голос
/ 02 января 2012

Используйте java.util.concurrent.SingleThreadExecutor и сделайте Runnable из каждой HTTP-операции и обработчика результатов. Вы можете отправлять в него последующие задачи, определяя, нужно ли вам продолжать выполнение.

Например, HTTP «задача» будет запускаться и отправлять результат «задача» в случае успеха или ошибку «задача» в случае ошибки. Задача Result, в свою очередь, отправит другую задачу HTTP после завершения обработки. Использование SingleThreadExecutor обеспечивает одновременное выполнение только одной задачи.

Вы можете использовать ThreadPoolExecutor, если вы можете обрабатывать сразу несколько операций в полете.

Возьмите все это и оберните его в AsyncTask, который управляет "стартовым" уровнем верхнего уровня и ждет, пока все завершится Вероятно, было бы полезно иметь ConditionVariable или что-то еще, чтобы синхронизировать сигнал «конец» (используя «задание» «Готово»), чтобы вы могли смело разрушать Executor.

A SynchronousQueue не делает здесь ничего полезного, потому что оставляет вам все управление протектором. Если вы используете Executor, который все обрабатывается, и все, с чем вы имеете дело, это Runnable s и Future s. Вероятно, поэтому вы не найдете никаких учебных пособий. В любом случае, все Executor используют одну из этих реализаций очереди!

В соответствии с запросом приведен скелетный Java-код. Неподдерживаемый непроверенный как есть. Это должно начать вас. Вы можете использовать другой объект синхронизации, если вам не нравится ConditionVariable.

Это общая методика, не специфичная для Android, не стесняйтесь использовать ее в других контекстах.

Это функционирует как конечный автомат, с HttpTask и другими, формирующими состояния, а переходы жестко закодированы путем отправки следующего состояния в ExecutorService. Есть даже «Большой взрыв в конце, так что все знают, когда хлопать» в виде ConditionVariable.

Некоторые могут считать DoneTask и FailedTask излишним, но он поддерживает согласованность механизма Next State и позволяет Future<? extends ResultTask> функционировать как довольно безопасный для типов контейнер для результатов, и, конечно, удерживает вас от неправильного назначения это.

abstract class BasicTask {
    final ExecutorService es;
    final ConditionVariable cv;
    public BasicTask(ExecutorService es, ConditionVariable cv) {
        this.es = es;
        this.cv = cv;
    }
}
abstract class HttpTask extends BasicTask {
// source omitted.
// you should make a class to prepare e.g. Apache HTTP resources for specific tasks (see below).
}
abstract class ResultTask implements Runnable {
    final ConditionVariable cv;
    public ResultTask(ConditionVariable cv) {
        this.cv = cv;
    }
    public void run() {
        cv.open();
    }
}
final class FailedTask extends ResultTask {
    final Exception ex;
    public FailedTask(ConditionVariable cv, Exception ex) {
        super(cv);
        this.ex = ex;
    }
    public Exception getError() { return ex; }
}
final class DoneTask<T> extends ResultTask {
    final T results;
    public DoneTask(ConditionVariable cv, T results) {
        super(cv);
        this.results = results;
    }
    public T getResults() { return results; }
}

class HttpSequence extends AsyncTask<Void,Void,Object> {
    // this will capture the ending task
    Future<? extends ResultTask> result;
    // this is an inner class, in order to set Result. Refactor so these are small.
    // if you don't like inner classes, you still need to arrange for capturing the "answer"
    final class SomeHttpTask extends HttpTask implements Runnable {
        public void run() {
            try {
                final SomeType thisStep = doTheStuff(lastStep);
                if(thisStep.isDone()) {
                    // we are done here
                    result = es.submit(new DoneTask<SomeType>(cv, thisStep));
                }
                else if(thisStep.isFailed()) {
                    // not done: we can't proceed because of something in the response
                    throw thisStep.getError();
                }
                else {
                    // not done, everything is ok for next step
                    es.submit(new NextHttpTask(es, cv, thisStep));
                }
            }
            catch(Exception ex) {
                result = es.submit(new FailedTask(cv, ex));
            }
        }
    }
    final class TheFirstTask extends HttpTask implements Runnable {
    // source omitted.  just emphasizing you need one of these for each "step".
    // if you don't need to set Result, this could be a static inner class.
    }

@Override
public Object doInBackground(Void...) {
    final ExecutorService es = Executors.newSingleThreadExecutor();
    final ConditionVariable cv = new ConditionVariable(false);
    try {
        es.submit(new TheFirstTask(es, cv));
        // you can choose not to timeout at this level and simply block until something happens...
        final boolean done = cv.block(timeout);
        if(!done) {
            // you will need to account for unfinished threads, see finally section!
            return new IllegalStateException("timed out waiting on completion!");
        }
        if(result != null) {
            final ResultTask done = result.get();
            if(done instanceof DoneTask) {
                // pass SomeType to onPostExecute()
                return ((DoneTask<SomeTYpe>)done).getResults();
            }
            else if(done instanceof FailedTask) {
                // pass Exception to onPostExecute()
                return ((FailedTask)done).getError();
            }
            else {
                // something bad happened, pass it to onPostExecute()
                return new IllegalStateException("something unexpected signalled CV!");
            }
        }
        else {
            // something bad happened, pass it to onPostExecute()
            return new IllegalStateException("something signalled CV without setting result!");
        }
    }
    catch(Exception ex) {
        // something outside workflow failed, pass it to onPostExecute()
        return ex;
    }
    finally {
        // naive shutdown (doesn't interrupt running tasks): read JavaDoc on ExecutorService for details
        es.shutdown();
    }
}
@Override
public void onPostExecute(Object result) {
    if(result instanceof SomeType) {
        // success UI
    }
    else if(result instanceof Exception) {
        // error UI
    }
}
}
0 голосов
/ 01 января 2012

Я не могу сказать наверняка, не зная подробностей вашего варианта использования, но вы, вероятно, хотите избежать SynchronousQueue, поскольку он будет блокировать поток, помещая вещи в очередь, пока поток слушателя не заберет его обратно из очереди. , Если бы вы вводили вещи с помощью потока пользовательского интерфейса, вы бы заблокировали пользовательский интерфейс.

Я думаю, что BlockingQueue может удовлетворить ваши потребности. JavaDoc имеет хороший пример производителя-потребителя.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...