Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Task queue on Android like in GCD on iOS?

Tags:

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?

like image 291
Kostia Dombrovsky Avatar asked Jul 08 '11 10:07

Kostia Dombrovsky


People also ask

How many types of queues are there in iOS?

There are four such queues with different priorities : high, default, low, and background.

What is global queue in iOS?

A dispatch queue that is bound to the app's main thread and executes tasks serially on that thread. typealias dispatch_queue_global_t. A dispatch queue that executes tasks concurrently using threads from the global thread pool.

What is dispatch queue in iOS?

Dispatch queues are FIFO queues to which your application can submit tasks in the form of block objects. Dispatch queues execute tasks either serially or concurrently. Work submitted to dispatch queues executes on a pool of threads managed by the system.

How do I turn off GCD in Swift?

In your comments, you mention that you were using NSTimer , a.k.a. Timer in Swift. If you want to stop a timer, call timer. invalidate() to stop it. Create a new NSTimer when you want to start it again.


2 Answers

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.

like image 75
Tuxdude Avatar answered Mar 11 '23 10:03

Tuxdude


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.

like image 45
Risch Avatar answered Mar 11 '23 12:03

Risch