3

So, as the title says: What actually happens when you post a runnable from another thread onto the main thread?

I've seen lots of questions asking how you do it, and how the basics of it work. But I've had a hard time finding an exact explanation of what actually happens when you put a runnable on the MessageQueue. It runs when it is the Runnable's turn, sure. But when is this?

So for example:
Assuming that there's a button that launches an ASync request, and the request returns and triggers a runnable/callback that runs on the MainThread. What happens? The runnable gets added to a MessageQueue and runs when it is 'time'. But when is it 'time'? What if I press another button that does some semi-long blocking task on the MainThread just before the Async request posts the runnable on MainThread? Does it wait until the logic on my blocking button is completed? Does it interrupt it? Does it interweave the runnable code with the code of my blocking-code button? What happens exactly?

Main reason I'm asking is so I can get a better understanding of what considerations I need to keep in mind to prevent errors due to multithreading. (Specifically the case of old requests affecting pages that have already been refreshed)

ZeroStatic
  • 364
  • 1
  • 3
  • 13

2 Answers2

4

First, you need to understand what the Message class is like. A Message object contains among other fields the following:

    Handler target;     // a handler that enqueued the message
    long when;          // the time at which the message is to be processed 

    [RUNNABLE] Runnable callback;   =
    [SWITCHED] int what, int arg1, int arg2, Bundle data...

    bool isAsynchronous; // I will talk about it in the end

What I tagged with [RUNNABLE] and [SWITCHED] represents the two non-overlapping means of processing a Message. If the callback is not null all the [SWITCHED] fields are ignored. If the callback is null than the Message is defined by [SWITCHED] fields and is processed in either the Handler's overriden handleMessage() or the handleMessage() of the Handler.Callback the handler was initialized with.

The MessageQueue is sorted by the when field. The Looper will not dequeue and process a message until the current time, as measured by SystemClock.uptimeMillis, is greater than or equal to the time stored in the message’s when field.

When you call Handler#post(Runnable r) the following things happen:

  1. A Message is obtained from the pool (a simple static linked list in the Message class)

  2. Your Runnable is assigned to the message's callback field.

  3. when field is simply set to the current time if no delay or specific time was passed

  4. The Message is enqueued to the MessageQueue. If when is earlier than that of the head of the queue it becomes a new head. If it's not, than it's inserted in the middle so that the MessageQueue remains sorted by when

  5. The Looper which was in a non-terminating loop dequeuing the messages from the queue and processing them in sequence (no interweaving), eventually, dequeues our message and calls dispatchMessage() on the handler that originally posted the Runnable.

  6. The handler decides whether the message is [RUNNABLE] or [SWITCHED] and processes it accordingly. In particular it calls run() on the callback if it's present

This should answer your questions on the behavior of your Runnable posted on the UI Thread during the blocking task - well, no, it does not interrupt the ongoing task, nor does it interweave. Everything that happens on the thread first gets into the MessageQueue, button clicks or your custom Runnables that you post from other threads. There is basically no way it could be happening some other way: Looper.loop() just makes the thread busy with its for(;;) loop.

There are ways to change the messages ordering though.

For instance, there is an interesting concept of sync-barrier in the Looper/Handler framework. A sync-barrier is by a convention just a Message with a null target (so it's basically just a flag-like thing, there is no handler to dispatch it). If it's put to the queue with postSyncBarrier(), the whole process of dequeuing changes, until the sync-barrier is removed from the queue with removeSyncBarrier(). The Messages not marked as isAsynchronous will be ignored and not dequeued and processed at all. Instead, the queue will be scanned until the message with isAsynchronous = true is found. It will then be scheduled according to its when and processed when its time comes.

Also, you can call a self-explanatory Handler#postAtFrontOfQueue(), though, as pointed out in the documentation

This method is only for use in very special circumstances -- it can easily starve the message queue, cause ordering problems, or have other unexpected side-effects.

I suggest you browse the source code of all the classes mentioned. It reads like a good fiction book.

Varvara Kalinina
  • 2,043
  • 19
  • 29
1

There are bunch of other runnables that the MainThread executes, such as updating the UI, touch events. The 'time' is when the posted runnable is ready to be dequeued. If any other runnable came before it, your runnable will wait.

There is no such thing here as interruption. Your button will submit burst of runnables, as same as submitting same number of runnables from many different threads.

If you have a message that is non-short (whatever contains LONG word is bad for UI) operation will block the execution of other recurrent tasks submitted in the queue, most of often demonstrated with no updates (for task that execution is indeterminate) on the UI at all or junking if it is matter of burst of runnables that execution takes longer than 8ms.

Nikola Despotoski
  • 49,966
  • 15
  • 119
  • 148
  • Do you have any references for this? It seems very interesting and I wonder where the exact numbers come from, etc. – Acapulco Dec 07 '15 at 05:41
  • I'm assuming that the listeners for every one of these events get wrapped in one big runnable when they are triggered? Or are there also cases where the system tries to split our own-written code up in multiple runnables to 'share' time with other runnables? If so, is there any way to see how this system works? – ZeroStatic Dec 07 '15 at 07:52
  • @ZeroStatic 1st question, short answer No. Long: I'm not quite sure how events are pipelined. 2nd question: No it does not split, maybe on linux kernel does this. 3rd question: See the source of `MessageQueue` how runnables are queued for execution. @Acapulco, I'm just interpreting the source and this diagram http://i.imgur.com/EIOk5Gq.png – Nikola Despotoski Dec 07 '15 at 09:28