Smart task queue

Do you use background threads (via Thread class, AsyncTask class, a service, etc.) for background tasks? Of course you do. Most of us do. It’s the most popular way of getting things done while keeping the app responsive. But does it mean smooth? Take a look at another approach.

Game programmers are used to looking at applications from a slightly different angle. Games usually run at a constant frame rate (let’s say 60fps) to give the best visual and gaming experience. A typical game loop looks like this:

while(running){
	checkInput()
	doPhysics()
	updateState()
	renderGraphics()
}

It’s probably not the best game loop, but I hope you get the point. Each loop has to finish in ~16ms in order to keep 60 frames per second. All tasks are split to small chunks and executed in a loop 60 times per second. Modern games often use additional threads for artificial intelligence or world streaming, but these threads have to be synchronized and limited as well so the game can run at 60fps.

Android is different. Of course there’s a main thread, a message loop and background threads for other tasks, but nobody cares about it that much. Android applications are event-driven and refresh only when the UI is invalid. Background tasks are usually executed in separate background threads. It means that a background task tries to take roughly the same amount of CPU power no matter if it slows down drawing the UI or not. Take a look at the following chart:

cpu usage

See the areas where blue and red overlap? These are spikes in the drawing code which are not properly handled by the generic background threads. Due to that, although there’s a lot of spare CPU power, the application doesn’t run with constant 60fps. Wouldn’t it be better to give less CPU power to the background thread when the app is busy drawing UI and more when it’s idle? Like this:

cpu usage 2

My idea is to synchronize background task execution with drawing, compute the average frame time and execute tasks either when there’s enough time before next frame or when the device is 100% idle.

Synchronization

To capture every draw we can use ViewTreeObserver.onPreDraw. The pseudocode is as follows:

onPreDraw(){
    if(frameTime<averageTime||frameTime>idleTime){
        notifyTaskThread()
    }else{
        pauseTaskThread();
    }
}

To make it work we need a synchronized queue, an execution thread and a bunch of methods to queue/dequeue tasks.

Average frame time

Every phone runs at a different fps and we need this value to adjust task execution. With ViewTreeObserver it’s very easy to compute it, for example like this:

public class AverageValue {
    double value = 0;
    int values = 0;

    public void add(long v) {
        if (v < 10 || v > 100)
            return;
        if (values == 0) {
            value = v;
            values = 1;
        } else {
            value = (value * values + v) / (values + 1);
            values = Math.min(values + 1, 100);
        }
    }

    public long get() {
        return (long) value;
    }
}

I’m intentionally skipping too low and too high values. Frames shorter than 10ms are probably not going to happen in reality. If the drawing time is longer than 100ms the application is idle or under heavy stress, which shouldn’t contribute to average frame time as well.

Idle state

Running code between frames is nice, but the best time for code execution is when the application is 100% idle – not drawing anything. How to detect that? Well, I would say that it’s safe to wait a fixed amount of time and assume that if drawing didn’t happen, the application is idle and probably won’t draw anytime soon.

How much time to wait? In my sample code it’s 500ms, but there’s nothing behind it. Google would probably test that value on ~1M random apps from the Play Store. Unfortunately there’s no good way to measure the average idle state time.

Implementation

The code is on GitHub (hooray, what a great time to live in!), so I won’t paste it here. My implementation adds support for frame tasks – tasks executed every frame, no matter what. In ideal implementation such task queue should support tasks with predicted execution time in order to pick them according to size to fill idle gaps between frames.

https://github.com/ZieIony/TaskQueue

To check it out, you can use Jitpack. Add it in your root build.gradle at the end of repositories:

allprojects {
    repositories {
	...
	maven { url "https://jitpack.io" }
    }
}

And add the dependency:

dependencies {
    compile 'com.github.ZieIony:TaskQueue:9cf6d017d5'
}

I’m using such queues for analyzing and saving data in background, but I’m sure there are more cases where it can be useful.

Leave a comment

Design a site like this with WordPress.com
Get started