Android: what does publishing runnable from another thread to the main thread actually do?

So, as the title says: what actually happens when you send runnable from another thread to the main thread?

I have seen many questions asking how you are doing this and how the basics of his work are. But it was hard for me to find an exact explanation of what actually happens when you type runnable in MessageQueue. Of course, it works when it's a runnable turn. But when is it?

So for example:
Suppose there is a button that launches an ASync request, and the request returns and starts a runnable / callback, which runs on MainThread. What's happening? The function being executed is added to the MessageQueue and is launched when it is "time". But when is the "time"? What if I press another button that performs some semi-long blocking task on MainThread just before the Async request sends a runnable to MainThread? Wait until the logic of my lock button is complete? Does it interrupt? Does it flush the startup code with the code for my lock button? What exactly is going on?

The main reason I ask is because I can better understand what considerations I should consider in order to prevent errors due to multithreading. (In particular, the case of old queries affecting pages that have already been updated)

+2
source share
2 answers

First, you need to understand what the Message class is. The 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 noted with [RUNNABLE] and [SWITCHED] are two non-overlapping Message processing tools. If the callback value is not null, all [SWITCHED] fields are ignored. If the callback is null than Message , defined by the [SWITCHED] fields and processed either in Handler's overriden handleMessage() or in handleMessage() Handler.Callback , the handler was initialized with.

MessageQueue sorted by when field. Looper does not delete and process the message until the current time measured by SystemClock.uptimeMillis is greater than or equal to the time stored in the when message field.

When you call Handler#post(Runnable r) , the following happens:

  • A Message obtained from the pool (a simple static linked list in the Message class)

  • Your Runnable assigned to a callback message.

  • when field is simply set to the current time, if there is no delay or a certain time has passed

  • Message is placed in the MessageQueue . If when earlier than the head of the queue, he becomes a new head. If it is not, it is inserted in the middle so that the MessageQueue remains sorted when

  • Looper , which was in a loop without end, discarding messages from the queue and processing them sequentially (without weaving), finally cancels our message and calls dispatchMessage() on the handler that originally placed Runnable .

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

This should answer your questions about the behavior of your Runnable published in the user interface thread during the lock task - well, no, it does not interrupt the current task and does not bind . Everything that happens in the thread first gets into a MessageQueue , button click, or your custom Runnables that you send from other threads. Basically, there is no way this could happen in any other way: Looper.loop() just makes the thread busy with its for(;;) loop.

There are ways to reorder messages.

For example, there is an interesting sync cable concept in the Looper / Handler structure. The synchronization barrier is in agreement only with Message with a target value of zero (so this is basically just a flag thing, there is no handler to send it). If it is queued using postSyncBarrier() , the entire redefinition process changes until the sync cable is removed from the queue using removeSyncBarrier() . Messages , not marked as isAsynchronous , will be ignored, not deleted and processed at all. Instead, the queue will be checked until a message with isAsynchronous = true . Then it will be scheduled according to its when and processed when the time comes.

Alternatively, you can call the friendly Handler#postAtFrontOfQueue() , though, as stated in the documentation

This method is intended only for special cases - it can easily starve in a message queue, cause problems with ordering, or other unexpected side effects.

I suggest you look at the source code of all the mentioned classes. It reads like a good art book.

+2
source

There are many other runnables that MainThread performs, such as updating the user interface, touch events. "Time" is when the displayed runnable is ready to exit the queue. If any other runnable came before it, your runnable will wait.

There is no such thing as interruption. Your button will send the runnables package, as well as send the same number of runnables from many different threads.

If you have a message that is not short (what contains the LONG word is bad for the UI), the operation blocks the execution of other repetitive tasks sent to the queue, most of which are often shown without updates (for the task, undefined) in the user interface in all cases or at startup, when it comes to the runnables package, which takes more than 8 ms to complete.

+1
source

Source: https://habr.com/ru/post/944735/


All Articles