Очередь задач на Android как в GCD на iOS? - PullRequest
19 голосов
/ 08 июля 2011

Есть ли такая вещь, как очередь задач на Android?Я знаю, что это может быть написано от руки, но есть ли готовая библиотека для этого?

Ответы [ 6 ]

9 голосов
/ 24 сентября 2015

Я не уверен, будет ли для этого библиотека, так как Android уже предоставляет высокоуровневые строительные блоки для того, чего вы пытаетесь достичь.

Обработчик

Если я вас правильно понял, вы хотите публиковать задачи из любого потока, чтобы ставить их в очередь и выполнять по отдельности в отдельном потоке. Это то, для чего предназначен Android Handler.

Ключевые черты Handler, Looper и MessageQueue

  • Обработчик привязан к одному Looper.
  • Каждый Looper имеет связанный MessageQueue
  • Обработчик использует нижнюю часть Looper для постановки в очередь и удаления сообщений потокобезопасным способом в Looper 'MessageQueue.
  • Объекты-обработчики по своей природе поточно-ориентированы и, следовательно, могут безопасно передаваться другим потокам.
  • Вы можете иметь несколько Handler объектов, привязанных к одному и тому же Looper. Это полезно, если вы хотите обрабатывать разные виды сообщений, используя разные обработчики. В этом случае вам гарантируется, что только один из обработчиков обработает сообщение / Runnable для данного Looper. Looper отвечает за отправку сообщения вправо Handler.
  • Если вы уже знакомы с парадигмой очереди сообщений для связи между двумя потоками (или схожим шаблоном буферизованного канала golang), Handler - это просто класс высокого уровня, который позволяет легко использовать этот шаблон.

Пример использования обработчика для отправки / получения сообщений, публикация Runnables

// BEGIN One-time Initialization
// Create a Handler thread
// This provides the looper for the Message Queue and
// will be processing all your messages (i.e. tasks).
handlerThread = new HandlerThread("SomeThreadName");

// Start the Handler Thread
// The thread will block (using the looper) until it
// receives a new message
handlerThread.start();

// Create a Message Handler which you can use to
// post and process messages
// The same Handler can also be used to post a Runnable which will get
// executed on handlerThread
handler = new CustomHandler(mHandlerThread.getLooper());
// END One-time Initialization

// Different ways to post a message to the Handler Thread
// These calls are thread-safe, can be called safely and
// concurrently from multiple threads without race conditions
handler.sendEmptyMessage(MESSAGE_ID_1);
handler.sendEmptyMessage(MESSAGE_ID_2);
handler.sendMessage(handler.obtainMessage(MESSAGE_ID_3, obj1));
handler.sendMessage(handler.obtainMessage(MESSAGE_ID_4, value, obj1));
handler.sendMessage(handler.obtainMessage(MESSAGE_ID_5, value1, valu2, obj1));

// Post a runnable on the Handler Thread
// This is thread-safe as well
// In fact all methods on the Handler class are thread-safe
handler.post(new Runnable() {
    @Override
    public void run() {
        // Code to run on the Handler thread
    }
});

// A skeleton implementation for CustomHandler
// NOTE: You can use the Handler class as-is without sub-classing it, if you
// intend to post just Runnables and NOT any messages
public class CustomHandler extends Handler {
    public CustomHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message message) {
        if (message != null) {
            // Process the message
            // The result can be sent back to the caller using a callback
            // or alternatively, the caller could have passed a Handler
            // argument, which the Handler Thread can post a message to

            switch (message.what) {
                case MESSAGE_ID_1:
                    // Some logic here
                    break;
                case MESSAGE_ID_2:
                    // Some logic here
                    break;
                case MESSAGE_ID_3:
                    // Some logic here
                    break;
                case MESSAGE_ID_4:
                    // Some logic here
                    break;
                case MESSAGE_ID_5:
                    // Some logic here
                    break;
                // Add more message types here as required
            }
        }
    }
}

// After you're done processing all messages and you
// want to exit the Handler Thread
// This will ensure that the queue does not accept any
// new messages, and all enqueued messages do get processed
handlerThread.quitSafely();

Отклонения от приведенного выше примера

  • Хотя я использовал HandlerThread в приведенном выше примере, его использование не обязательно. Вы даже можете напрямую использовать вызовы Looper, то есть Looper.prepare() и Looper.loop(), чтобы запустить свой собственный цикл сообщений в потоке.
  • Как уже упоминалось в комментариях, вам не нужно подразделять акции Handler, если вы не собираетесь обрабатывать какие-либо сообщения.
  • Вы можете легко общаться между несколькими потоками, используя Handler для каждого потока, которому необходимо получить сообщение.
  • В Handler есть методы для планирования доставки сообщений и выполнения в будущем.

Платформа Android внутренне использует Handler для управления событиями жизненного цикла компонентов (onPause, onResume и т. Д.).

AsyncTask

AsyncTask - еще одна альтернатива планированию задач в другом потоке. , Я не буду вдаваться в подробности его реализации, поскольку документация для разработчиков Android уже описывает его подробно.

Я обычно использую AsyncTasks для задач, которые, как я знаю, будут использовать фоновый поток в течение длительного времени (легко> = 100 мс как минимум). Вот некоторые примеры, относящиеся к этой категории: Binder IPC, RPC-вызовы, сетевые вызовы, фоновые загрузки и т. Д.

С другой стороны, Handler более приспособлен для ситуаций, сосредоточенных на обработке большего количества сообщений как можно быстрее. Другими словами, избегайте выполнения какой-либо операции блокировки в handleMessage(). Вы можете легко написать код без блокировки, используя Handler, он управляет всеми блокировками за вас при постановке в очередь и снятии с очереди сообщений.

Фактически AsyncTask можно использовать в комбинации с Handler, разбивая работу на быструю часть (заботится Handler) и медленную часть (заботится AsyncTask).

PS: Хотя это и касается вопроса, если вас интересует парадигма очереди сообщений; взгляните на LMAX Disruptor, высокопроизводительную библиотеку очереди сообщений между потоками. Их проектный документ довольно хорошо объясняет, какие части очереди сообщений нуждаются в блокировке / атомарном доступе.

3 голосов
/ 16 июля 2013

Я также искал что-то вроде GCD для Android. Хотя Handlers и AsyncTasks великолепны, красота GCD (по моему скромному мнению) заключается в том, что вы можете распределять нагрузку на фоновый поток, чтобы выполнять тяжелую работу. Когда выполнение выполнено, я легко могу выполнить обновления пользовательского интерфейса в потоке пользовательского интерфейса.

Поскольку я ничего не нашел, я и мой школьный товарищ решили создать свой собственный. Вы можете найти его по адресу: ICDispatch на github

По сути, все, что вам нужно сделать, это объявить класс Application, который расширяет ICDispatchApplication вместо Application, и когда вы хотите отправить что-то, вы просто вызываете

App.executeOn(int queue, ICBlock block);

Пример:

App.executeOn(ICDispatch.NORMAL, new ICBlock(){
   public void run(){
      //do stuff...
      App.executeOn(ICDispatch.MAIN, new ICBlock(){
          public void run(){
             //post result to UI thread.
          }
      }
   }
});

Хуже всего то, что будет много отступов. Чтобы минимизировать отступы, вы можете использовать лямбда-нотацию:

App.executeOn(ICDispatch.NORMAL, ()->{
    //do stuff...
    //do some more...
    //then even more
    App.executeOn(ICDispatch.MAIN,() -> {
       //Post result on UI thread.
    }
});

В настоящее время ICDispatch поддерживает очереди в НИЗКОМ, НОРМАЛЬНОМ, ВЫСОКОМ, ОСНОВНОМ И СОГЛАСОВАННОМ. Функции будут добавлены по мере их реализации.

3 голосов
/ 08 июля 2011

Я не знаю iOS, поэтому не уверен, что он такой же, но в Android у вас есть ScheduledThreadPoolExecutor

1 голос
/ 24 ноября 2016

Я беру этот пример из кода Telegram:

Вы можете объявить расширенный поток для этого подхода

public static volatile DispatchQueue globalQueue = new DispatchQueue("globalQueue");

класс:

import android.os.Handler;
import android.os.Looper;
import android.os.Message;

import java.util.concurrent.CountDownLatch;

public class DispatchQueue extends Thread {

    private volatile Handler handler = null;
    private CountDownLatch syncLatch = new CountDownLatch(1);

    public DispatchQueue(final String threadName) {
        setName(threadName);
        start();
    }

    private void sendMessage(Message msg, int delay) {
        try {
            syncLatch.await();
            if (delay <= 0) {
                handler.sendMessage(msg);
            } else {
                handler.sendMessageDelayed(msg, delay);
            }
        } catch (Exception e) {
            FileLog.e("tmessages", e);
        }
    }

    public void cancelRunnable(Runnable runnable) {
        try {
            syncLatch.await();
            handler.removeCallbacks(runnable);
        } catch (Exception e) {
            FileLog.e("tmessages", e);
        }
    }

    public void postRunnable(Runnable runnable) {
        postRunnable(runnable, 0);
    }

    public void postRunnable(Runnable runnable, long delay) {
        try {
            syncLatch.await();
            if (delay <= 0) {
                handler.post(runnable);
            } else {
                handler.postDelayed(runnable, delay);
            }
        } catch (Exception e) {
            FileLog.e("tmessages", e);
        }
    }

    public void cleanupQueue() {
        try {
            syncLatch.await();
            handler.removeCallbacksAndMessages(null);
        } catch (Exception e) {
            FileLog.e("tmessages", e);
        }
    }

    @Override
    public void run() {
        Looper.prepare();
        handler = new Handler();
        syncLatch.countDown();
        Looper.loop();
    }
}

и вызывающий:

globalQueue.postRunnable(new Runnable() {
                                        @Override
                                        public void run() {
                                            /* do here what you want */
                                        }
                                    });
1 голос
/ 07 января 2015

Для тех, кто находит эту тему сейчас, доступна новая структура под названием Болты .Он имеет задачи и продолжения и может ждать завершения нескольких задач, например GCD.

0 голосов
/ 21 февраля 2013

Вы должны проверить Handler & Loopers

Обработчики по умолчанию (*), например dispatch_get_main_queue (), и вы можете опубликовать любой блок (Runnable экземпляр) кода. Тот же самый подход также приобретен с Context.runOnUiThread () и View.post (Runnable)

(*) Конструктор по умолчанию Handler наследует экземпляр Looper текущего потока (RunLoop в iOS) и очереди (через методы handlerInstace.post ... ()) Runnable экземпляры на Looper.

Для более предварительного использования. Вы можете создать свой собственный экземпляр Looper (имейте в виду, что это немного сложно :)). Тем не менее, это может быть удобно ...

Также для более продвинутого использования, обработчики являются лучшими инструментами, с которыми я сталкиваюсь на Android (и да, я скучаю по ним на iOS) для обмена сообщениями внутри приложения (межпроцессное взаимодействие, что-то, я думаю). Они могут быть настроены для обработки сообщений, бла, бла ...

...