Android – Introduction to Parallel Computing

Handling asynchronously is a ‘must’ for all mobile applications when executing any long time operation. For example, in Android, when a network operation is going to be called, it has to be executed on other thread (not UI thread). If the developers try to run a network operation on UI thread, Android OS will force the app to stop with an exception of android.os.NetworkOnMainThreadException. As usual, the developers will put long time operation in a AsyncTask (or a Thread) and let it run. I also introduced this class in many Android posts before. However I have only one AsyncTask running at a time in all of examples. A reader asked me if I can create a list of AsyncTask and execute them at the same time. Something likes Parallel Computing? The answer is ‘Yes, of course’. We can do that in Android. Therefore, in this post, I would like to show how I simulate a multi-threading case with AsyncTask and some experimental cases for comparison between native Java and Android Java in Parallel Computing

1. Prerequisites

I used AndroidAnnotations for shortening source code and keeping code structure easy to maintain. If you don’t know what it is, then read this post first before continuing Android – Introduction to AndroidAnnotations, Maven in Intellij IDEA

Why do we need Parallel Computing? If you followed this demo before Android – Show images from Flickr with ImageGridView, you can see that the images are downloaded one by one in an AsyncTask. We can speed up this download progress by creating many AsyncTask to download them simultaneously.

2. ParallelAsyncTask

In the demo code, you’ll get 4 activities: one MainActivity and 3 example activities. The MainActivity is just a simple ListActivity which is used as the start point to all examples. Only the first example is useful and can be applied in real app. The other two are just experimental cases because I would like to show the weird differences with multi-threading when running same code in native Java and Android Java.

MainActivity

The first example Parallel Async Task generates randomly a list of seven HDTask. This object contains a start and end number which are also randomly set.

public class HDTask {
    public HDTask(int id) {
        this.id = id;
        Random random = new Random();
        start = random.nextInt(10);
        finish = start + 50 + random.nextInt(50);
        description = String.format("Task %d run from %d to %d",id, start,finish);
    }

    public String getDescription() {
        return description;
    }

    String description;

    public int getId() {
        return id;
    }

    int id;

    public int getStart() {
        return start;
    }


    int start;

    public int getFinish() {
        return finish;
    }


    int finish;

}

ParallelAsyncTask

By clicking Execute tasks…, the apps will start for each HDTask an AsyncTask and all of them will be asynchronously executed. This SimpleTask just count by 1 from start to end after period of 1 second.

@EActivity(R.layout.parallelasynctask)
public class ParallelAsyncTaskActivity extends ListActivity {
    private ArrayList<HashMap<String, String>> taskListDisplay;
    private List<HDTask> taskList;
    private List<SimpleTask> asyncTaskList;
    private int taskCount = 7;
    private SimpleAdapter adapter;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @AfterViews
    void afterViews() {
        taskList = new ArrayList<HDTask>(taskCount);
        taskListDisplay = new ArrayList<HashMap<String, String>>();
        for (int index = 0; index < taskCount; index++) {
            HDTask hdTask = new HDTask(index);
            taskList.add(hdTask);
            HashMap<String, String> item = new HashMap<String, String>();
            item.put("Id", String.valueOf(index));
            item.put("Description", hdTask.getDescription());
            taskListDisplay.add(item);
        }

        adapter = new SimpleAdapter(ParallelAsyncTaskActivity.this, taskListDisplay, R.layout.list_item, new String[]{"Id", "Description"}, new int[]{R.id.textViewId, R.id.textViewName});
        setListAdapter(adapter);

    }

    @Click(R.id.buttonExecuteTasks)
    void executeTasks() {
        if (asyncTaskList != null)
        {
            for (SimpleTask simpleTask : asyncTaskList) {
                if (simpleTask.getStatus() == AsyncTask.Status.RUNNING) {
                    AlertMessageBox.Show(ParallelAsyncTaskActivity.this, "Error", "AsyncTasks are still running", AlertMessageBox.AlertMessageBoxIcon.Error);
                    return;
                }
            }
        }

        asyncTaskList = new ArrayList<SimpleTask>(taskList.size());
        for (Integer index = 0; index < taskList.size(); index++) {
            SimpleTask simpleTask = new SimpleTask();
            asyncTaskList.add(index, simpleTask);
            ParallelAsyncTask.execute(simpleTask, index);
        }
    }

    @Override
    protected void onStop() {
        if (asyncTaskList != null)
        {
            for (SimpleTask simpleTask : asyncTaskList) {
                if (simpleTask.getStatus() == AsyncTask.Status.RUNNING) {
                    simpleTask.cancel(false);
                }
            }
        }
        super.onStop();
    }

    class SimpleTask extends AsyncTask<Integer, String, String> {

        @Override
        protected String doInBackground(Integer... params) {
            HDTask task = taskList.get(params[0]);
            for (int index = task.getStart(); index <= task.getFinish(); index++) {
                HashMap<String, String> item = new HashMap<String, String>();
                item.put("Id", String.valueOf(params[0]));
                item.put("Description", task.getDescription() + " : " + index);
                taskListDisplay.set(params[0], item);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        adapter.notifyDataSetChanged();
                    }
                });

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (isCancelled())
                    return null;
            }
            return null;
        }
    }
}

Parallel Async Task

This asynchronous process works thanks to a helper class ParallelAsyncTask. In this class, I define a ThreadPoolExecutor and a static function for registering AsyncTask with this pool. All AsyncTask from this pool will be executed asynchronously later.

public class ParallelAsyncTask {
    private static final ThreadPoolExecutor executor;
    private static final int CORE_POOL_SIZE = 7;
    private static final int MAXIMUM_POOL_SIZE = 307;
    private static final int KEEP_ALIVE_TIME=1;
    private static final BlockingQueue<Runnable> blockingQueue;
    private static final ThreadFactory threadFactory;

    static
    {
        blockingQueue = new LinkedBlockingDeque<Runnable>(17);
        threadFactory = new ParallelAsyncTaskFactory();
        executor = new ThreadPoolExecutor(CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE_TIME, TimeUnit.SECONDS,blockingQueue,threadFactory);
    }
    public static <Param,Progress,Result> AsyncTask<Param,Progress,Result> execute(AsyncTask<Param,Progress,Result> task,Param...params)
    {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
            task.executeOnExecutor(executor, params);
        else
            task.execute(params);
        return task;
    }

    static class ParallelAsyncTaskFactory implements ThreadFactory
    {

        @Override
        public Thread newThread(Runnable r) {
            final AtomicInteger threadNumber = new AtomicInteger(1);
            return new Thread(r,"Parallel AsyncTask #" + threadNumber.getAndIncrement());
        }
    }
}

Please note that the executeOnExecutor function is added from API Level 11 so you can only use it from Honeycomb version. The ThreadPoolExecutor is an ExecutorService that executes each submitted task using one of possibly several pooled threads. The thread pools address two different problems: they usually provide improved performance when executing large numbers of asynchronous tasks, due to reduced per-task invocation overhead, and they provide a means of bounding and managing the resources, including threads, consumed when executing a collection of tasks (From Google)

3. Parallel.ForEach and Parallel.For

If you’re only interested in Parallel computing for Android, the previous section is all what you want. In this section, I just want to write about a weird problem (I use word weird for thing which I don’t understand 🙂 ) when I migrate the code for Parallel Computing from native Java to Android.

Let’s go back to our HDTask object in example above. I create a SimpleTask and initialize a new instance of SimpleTask for each HDTask object. But I want something should be as simple as in .NET. Something which I just need to call through a static function and the thread will be created without explicitly initializing any task object. Something like code listing below

final List<HDTask> taskList = new ArrayList<HDTask>(taskCount);
Parallel.ForEach(taskList, new ParallelAction<HDTask>() {
	@Override
	public void run(HDTask param) {
		for (int index=param.getStart();index < param.getFinish();index++)
		{
			System.out.println(param.getDescription() + " : " + index);
		}
	}
});

As you can see, this code structure is much more simpler and easy to maintain than before (the syntax is 100% as same as in .NET 🙂 ). We have a list of objects, a static function and an action which should be applied for each of objects in list. The static function is defined in Parallel class which uses ExecutorService and Future for executing threads in parallel.

public class Parallel {
    static int CPUs = Runtime.getRuntime().availableProcessors();
    private static final int TIME_OUT=1;


    public static <T> void ForEach(Iterable<T> params, final ParallelAction<T> action )
    {
        ExecutorService executorService = Executors.newFixedThreadPool(CPUs);
        ArrayList<Future<?>> futures = new ArrayList<Future<?>>();
        for (final T param:params)
        {
            Future<?> future = executorService.submit(new Runnable() {
                @Override
                public void run() {
                    action.run(param);
                }
            });
            futures.add(future);
        }

        for (Future<?> future:futures)
        {
            try {
                future.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

        executorService.shutdown();
        try {
            executorService.awaitTermination(1, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
...	
}

When this code run with native Java, all threads are really executed simultaneously as result below. They don’t have to wait for each other. All threads run at the same time.

Parallel in native Java

However if I move the same code to Android and run on emulator. Threads are waiting for each other. The next thread can only start when the previous one is finished

Parallel in Android

I have no idea why there is such difference. Maybe one of you understand the Android OS system better, can explain this phenomena. In source code section, Parallel.For is also available for experiment.

4. Conclusion

Parallel Computing is a complex field in programming. How to take advantage of multi CPU cores to speed up own app but not having effect on other applications, is always interesting task. The ParallelAsyncTask class bases on AsyncTask which introduced in this post, can be applied immediately in any real app. Parallel.ForEach and Parallel.For provide a much more beautiful syntax. However they still have to be improved so that they can execute tasks really in parallel. If you know the problem why Parallel.ForEach and Parallel.For don’t work in Android, please drop a comment below. Thank you.

Source code Android: https://bitbucket.org/hintdesk/android-introduction-to-parallel-computing
Source code Java: https://bitbucket.org/hintdesk/java-parallel-computing

Leave a Reply

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