Tuesday, August 27, 2013

How we implemented Async/await

You can now use async/await with dot42. This valuable language construct lets you write elegant code that looks exactly like synchronous code, but works asynchronously.

Example:

namespace AsyncAndroid
{
  [Activity]
  public class MainActivity : Activity
  {
    protected override void OnCreate(Bundle savedInstance)
    {
      base.OnCreate(savedInstance);
      SetContentView(R.Layouts.MainLayout);

      var myButton = FindViewById<Button>(R.Ids.myButton);
      myButton.Click += myButton_Click;
    }

    private async void myButton_Click(object sender, EventArgs e)
    {
      var status = FindViewById<TextView>(R.Ids.txtStatus);
      status.Text = "Waiting...";

      await Task.Factory.StartNew(() => DoSomethingLongRunning());

      status = FindViewById<TextView>(R.Ids.txtStatus);
      status.Text = "I'm back";
    }
  }
}

The above code implements a simple button click that starts a lengthy job and updates a text view to reflect the status. The click will always be handled on the UI thread. Doing something lengthy on the UI thread is a bad idea. With the await keyword, the method will return directly (to the UI thread) while the lengthy job is started on a background thread. When the lengthy job is ready, execution is resumed (on the UI thread).

To achieve the same with traditional threads, background workers, AsyncTasks, Loader's etc. you would be writing much less readable code.

Configuration changes

A problem that is specific to Android, is that of configuration changes. By default your activity will be destroyed and restarted when a configuration change happens. For example rotating your device will (by default) result in a new activity instance. This is fine unless you have a long running activity on some thread.

Let's say you want to download a large image and show the content in an ImageView afterwards. You typically start a background thread to download the image and pass the activity instance and perform a callback after the download has finished. However, if the activity was destroyed and recreated in the meantime (because of a configuration change), the callback will show the downloaded image in an ImageView that has been disconnected. The new activity instance will not show the downloaded image.

The same is still true if you use the following code:

var webClient = new WebClient();
var data = await webClient
                     .DownloadDataTaskAsync(myImageUrl);
var imageView = this.FindViewById<ImageView>(R.Ids.MyImage);
imageView.SetImageBitmap(DecodeBitmapData(data));

The async/await in the above code will ensure that execution resumes on the current UI thread, but not on the correct Activity instance (this.FindViewById will be invoked on the old activity instance).

To solve this problem, we have extended the Task API. Let's take a look at the same code and see how it works.

var webClient = new WebClient();
var data = await webClient
                     .DownloadDataTaskAsync(myImageUrl)
                     .ConfigureAwait(this);
var imageView = this.FindViewById<ImageView>(R.Ids.MyImage);
imageView.SetImageBitmap(DecodeBitmapData(data));

The additional call to ConfigureAwait(this) ensures that after await you will return on the original thread AND the this pointer will point to the latest activity instance.

To make this work you must add this line of code to the OnCreate method of your activity:

SynchronizationContext.SetSynchronizationContext(this);

Note that only the this pointer is updated. If you would hold on to an imageView instance before the await and use it after the await, you would still be updating the wrong instance of imageView.

Task scheduling

When you start a new Task, the scheduler decides what thread executes that task. In dot42 the task scheduler follows the following rules:
  1. Normal tasks will execute on a thread from a thread pool. By default this thread pool reserves multiple threads. In future versions we will refine this.
  2. Code following an await will continue on the thread that started the awaited task (even if the task was started inside asynchronous code). This may be refined in future versions.
  3. Tasks created with a LongRunning option will always execute on a new thread.
  4. Tasks created by Async methods in the dot42 .NET framework implementation (such as WebClient.DownloadDataTaskAsync) will run on threads of a seperate IO thread pool. This ensures that slow IO tasks will not be able to block your short running normal tasks.


5 comments:

  1. I tried this by porting my Xamarin sample (TtsSetup using async, from http://stackoverflow.com/questions/16779793/how-to-implement-callbacks-in-c-sharp-using-async-wait-with-xamarin-for-android) to Dot42, but it crashes on me. Part of the stack trace is:

    08-28 13:41:00.721 2127-2136/com.hyperionics.dot42Test E/System: Uncaught exception thrown by finalizer
    08-28 13:41:00.721 2127-2136/com.hyperionics.dot42Test E/System: java.lang.NullPointerException
    at java.util.concurrent.ThreadPoolExecutor.tryTerminate(ThreadPoolExecutor.java:638)
    at java.util.concurrent.ThreadPoolExecutor.shutdown(ThreadPoolExecutor.java:1330)
    at java.util.concurrent.ThreadPoolExecutor.finalize(ThreadPoolExecutor.java:1411)
    at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:187)
    at java.lang.Daemons$FinalizerDaemon.run(Daemons.java:170)
    at java.lang.Thread.run(Thread.java:856)
    08-28 13:41:00.721 2127-2127/com.hyperionics.dot42Test E/AndroidRuntime: FATAL EXCEPTION: main
    java.lang.ExceptionInInitializerError
    at system.Runtime.CompilerServices.AsyncTaskMethodBuilder1-16526219.Create(Unknown Source)
    at dot42Test.TtsAsyncActivity.CreateTtsAsync(d:\xamarin\dot42test\dot42Test\TtsAsyncActivity.cs)
    at dot42Test.VoiceSelectorActivity$GetEnginesAndLangsAsyncd__0-83817916.IAsyncStateMachine_MoveNext(d:\xamarin\dot42test\dot42Test\VoiceSelectorActivity.cs:126)
    at system.Runtime.CompilerServices.AsyncVoidMethodBuilder.Start(Unknown Source)
    at dot42Test.VoiceSelectorActivity.GetEnginesAndLangsAsync(d:\xamarin\dot42test\dot42Test\VoiceSelectorActivity.cs)

    Can share sources and whatever else I have. Eager to see it working!

    Greg

    ReplyDelete
    Replies
    1. Please email (support AT ...) your code to us and we'll investigate it.

      Delete
    2. Ewout, I sent you the project source code, and a full stack trace by email yesterday. It seems that it crashes deep in Dot42 libraries as soon as I try to call with async keyword another function, from a function which itself is declared as 'async', but see the code and trace. Eagerly awaiting your response and possible fixes, so that I could test further.

      Delete
    3. OK, upon some help form Dot42 support, I managed to compile and run my sample code correctly on ARM devices (Samsung GN2 with Android 4.1.1), but it still crashes the same on Intel Android emulators. I believe Dot42 devs are looking into it now. I'm pretty excited about all the goodies coming in Dot42!

      Delete
    4. Just to wrap up this thread: It turned out that the problem was not x86 related, but single CPU related. This was fixed and released September 2nd (1.0.1.75).

      Delete