Fork me on GitHub

Wednesday, November 13, 2013

Using scala.concurrent.Future on Android

From Scala 2.10, a concurrent execution function scala.concurrent.ops.spawn is deprecated, replaced with scala.concurrent.Future, and will be removed from Scala 2.11.

I rewrote my app with scala.concurrent.Future as an official Scala documentation suggests:
import ExecutionContext.Implicits.global
Future {
// ...
}

However, after I uploaded the app to Google Play, I received a ton of crash report:
java.lang.ExceptionInInitializerError
at scala.concurrent.impl.ExecutionContextImpl.createExecutorService(ExecutionContextImpl.scala:77)
at scala.concurrent.impl.ExecutionContextImpl.<init>(ExecutionContextImpl.scala:28)
at scala.concurrent.ExecutionContext$Implicits$.global$lzycompute(ExecutionContext.scala:63)
at scala.concurrent.ExecutionContext$Implicits$.global(ExecutionContext.scala:63)
at com.soundcorset.client.android.SoundcorsetService$Metronome.start(SoundcorsetService.scala:38)
...
Caused by: java.lang.Error: java.lang.NoSuchFieldException: parkBlocker
at scala.concurrent.forkjoin.ForkJoinPool.<clinit>(ForkJoinPool.java:2852)
... 20 more
Caused by: java.lang.NoSuchFieldException: parkBlocker
at java.lang.ClassCache.findFieldByName(ClassCache.java:510)
at java.lang.Class.getDeclaredField(Class.java:683)
at scala.concurrent.forkjoin.ForkJoinPool.<clinit>(ForkJoinPool.java:2847)
... 20 more

Scala library has its own copy of ForkJoinPool implementation, which includes dynamic invocations, that is parkBlocker in this case. Unfortunately, some Android devices does not have this method, so we've got this awful crash report.

The solution is very simple: Do not use ExecutionContext.Implicits.global. Declare a custom implicit ExecutionContext instead. For example:
implicit val exec = ExecutionContext.fromExecutor(
  new ThreadPoolExecutor(100, 100, 1000, TimeUnit.SECONDS,
    new LinkedBlockingQueue[Runnable]))

Future {
// ...
}

This works perfect in production.

EDIT: If the minimum API level of your app is above 11, consider using AsyncTask.THREAD_POOL_EXECUTOR instead of making your own thread executor:
implicit val exec = ExecutionContext.fromExecutor(
  AsyncTask.THREAD_POOL_EXECUTOR)
Thanks Niklas Klein for this suggestion.

13 comments:

  1. Many thanks. Looks very easy. One questions remain (at least to me as a Scala newbie). What if I want the continuation to run on the UI thread in order to manipulate some UI state on the callback? How can I achieve this?

    ReplyDelete
  2. runOnUiThread would be helpful:

    https://github.com/pocorall/scaloid#asynchronous-task-processing

    ReplyDelete
    Replies
    1. For sure... thanks... so sorry :-(

      Delete
    2. Hi, I'm also new to scaloid and trying to use future to replace the original AsyncTask.

      I'm wondering how to scala future deal with the issue that Activity got recreated before an async task finish and call the runOnUiThread()? In this case, all the objects when runOnUiThread() are pointers that out of date.

      Traditional way of using AsyncTask seems to be adding a task.cancel() in onStop(), does scala future or scaloid support this?

      Thanks!

      Delete
    3. This might be help:

      val active = false
      onDestroy(active = false)
      onCreate(active = true)

      future {
      doSomeTask()
      if(active) toast("finished")
      }

      Delete
    4. And if you want to do some task that is longer than activity lifecycle, consider using Service :-D

      Delete
  3. Sung-Ho: Your hint is very helpful. One small question: will this customized context affect performance on mobile device? Are there good values for those parameters?

    ReplyDelete
  4. The performance impact is not noticable. This app is made with Scaloid, try this yourself:

    https://play.google.com/store/apps/details?id=com.soundcorset.client.android

    ReplyDelete
  5. Why don't you place this useful info in the main doc: https://github.com/pocorall/scaloid/ ?

    ReplyDelete
    Replies
    1. Now I updated the document. Thanks for suggestion!

      Delete
  6. You should consider using a system Executor instead of creating your own. This way you stay compatible with Android's idea of concurrency.

    implicit lazy val ThreadPoolExecutionContext = ExecutionContext.fromExecutor( AsyncTask.THREAD_POOL_EXECUTOR )

    Also I've just written a Future implementation that comes with predefined (but still overridable) ExecutionContexts to run the body code on the above ThreadPool but run the callbacks on the UI thread.

    https://github.com/Taig/Android-Toolbelt/blob/master/src/main/scala/com/taig/android/concurrent/Future.scala

    ReplyDelete
  7. I didn't know about AsyncTask.THREAD_POOL_EXECUTOR. I updated the post. Thanks!

    ReplyDelete