Task queue on Android like in GCD on iOS?-ThrowExceptions

Exception or error:

Is there such a thing as task queue on Android? I know that it can be written by hand but is there a ready to use library for that?

How to solve:

I’m not sure if there would be a library for this one, as Android already provides the high-level building blocks for what you’re trying to achieve.

Handler

If I understood you correctly, you want to post tasks from any thread to be queued and executed one-by-one on a dedicated thread. This is very much what Android Handler is meant for.

Key traits of Handler, Looper and MessageQueue

  • A Handler is tied to a single Looper.
  • Each Looper has an associated MessageQueue
  • Handler uses a Looper underneath to enqueue and dequeue messages in a thread-safe manner into the Looper‘s MessageQueue.
  • Handler objects are inherently thread-safe and hence can be passed around to other threads safely.
  • You can have multiple Handler objects tied to a same Looper. This is useful if you want to process different kinds of messages using different Handlers. In this case, you are guaranteed that only one of the Handlers will process a Message/Runnable for a given Looper. The Looper takes care of dispatching the Message to the right Handler.
  • If you’re already familiar with the Message Queue paradigm for communicating between 2 threads (or similar golang‘s buffered channel pattern), Handler is just a high level class which lets you use this pattern easily.

Example for using Handler to send/receive Messages, post 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();

Deviations from the above example

  • Although I’ve used HandlerThread in the above example, it is not mandatory to use it. You can even use the Looper calls directly, i.e. Looper.prepare() and Looper.loop() to run your own message loop in a thread.
  • As already mentioned in the comments, you do not need to sub-class the stock Handler if you do not intend to handle any messages.
  • You can communicate between multiple threads easily by using a Handler for each thread that needs to receive the message.
  • There are methods in Handler to schedule message delivery and Runnable execution in the future as well.

Android’s framework internally uses Handler extensively for managing component lifecycle events (onPause, onResume, etc.).

AsyncTask

AsyncTask is another alternative to scheduling tasks on a different thread. . I won’t go into too much detail of its implementation, as the Android developer documentation already describes it in detail.

I usually use AsyncTasks for tasks that I know I’ll use a background thread for a long time (easily >= 100 ms at least). Some examples which fall into this category I can think of are Binder IPC, RPC calls, Network calls, Background downloads, etc.

On the other hand, Handler is more tailored for situations focussed on processing more number of messages as quickly as possible. In other words avoid performing any blocking operation in handleMessage(). You can write lock-free code easily using Handler, it manages all the locking for you when enqueuing and dequeuing messages.

In fact AsyncTask can be used in combination with Handler by splitting the work into a fast part (taken care by Handler) and a slow part (taken care by AsyncTask).

PS: Although tangential to the question, if you’re interested in the Message Queue paradigm; do take a look at LMAX Disruptor, which is a high performance inter-thread Message Queue library. Their design document explains pretty well, which parts of the Message Queue, need locking/atomic access.

###

I’ve also looked around for something like GCD for Android. While Handlers and AsyncTasks are awesome the beauty of GCD (in my humble opinion) is that you can dispatch a workload on a background thread to do the heavy lifting. When the execution is done it i easy to execute the UI updates on the UI thread.

Since I did not find anything me and my school mate decided to create one of our own.
You can find it at:
ICDispatch on github

Basically all you need to do is to declare an Application class that extends ICDispatchApplication instead of Application and when you want to dispatch something you just call on

App.executeOn(int queue, ICBlock block);

Example:

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

The worst part is that there will be a lot of indentation. In order to minimize indentation you could use lambda notation:

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

At the moment ICDispatch supports LOW, NORMAL, HIGH, MAIN and CONCURRENT queueing. Features will be added as they are implemented.

###

I don’t know iOS so I’m not sure if it is the same but in Android you have the ScheduledThreadPoolExecutor

###

For anyone finding this thread now, there is a new framework available called Bolts. It has tasks and continuations and can wait on multiple tasks to finish, like GCD.

###

I take this sample from Telegram Code :

You can declare extended thread for this approach

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

the class is :

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

and the Caller :

globalQueue.postRunnable(new Runnable() {
                                        @Override
                                        public void run() {
                                            /* do here what you want */
                                        }
                                    });

###

You should check Handler & Loopers

Handlers, by default (*), like dispatch_get_main_queue() and you can post any block (Runnable instance) of code. Same approach also acquired with Context.runOnUiThread() and View.post(Runnable)

(*) Default constructor of Handler inherits the current thread’s Looper instance (RunLoop in iOS) and queues (via handlerInstace.post…() methods) Runnable instances on Looper.

For more advance usage. You can create your own Looper instance (be aware it is a bit tricky :)). Still this might be handy…

Also for more advance usage, Handlers are the best tools i come across on Android (and yes, i miss them on iOS) for messaging inside application (inter-process communication something i guess). They might be customized to handle posted messages, bla, bla…

Leave a Reply

Your email address will not be published. Required fields are marked *