Xamarin の Alpha版で async/await を試す

Xamarin Blog で "Alpha版だけど async/await 使えるようになったよー" との事だったのでさっそく試してみました。

以前から、"Good news – we plan to release full support for async/await in all our products in April of 2013." と言われていたので予定どおりですかね。

Alpha版をインストール

Xamarin Studio のシステムメニュー → アップデートをチェック で、 "Update channel" で Alpha を選ぶとインストールできます。もちろん、アルファ版なので自己責任で。

インストール後の、Xamarin と各SDK のバージョンはこう↓なってました。

Xamarin Studio Version 4.0.2 (build 18) Installation UUID: xxxxxx Runtime: Mono 3.0.7 (master/514fcd7) GTK 2.24.16 GTK# (2.12.0.0) Package version: 300070000

Apple Developer Tools Xcode 4.6 (2066) Build 4H127

Xamarin.Mac Xamarin.Mac: Not Installed

Xamarin.Android Version: 4.7.0 (Trial Edition) Android SDK: /Users/hrnv/dev/sdks/android-sdk-macosx

Xamarin.iOS Version: 6.3.0.255 (Trial Edition) Hash: ba05545

同期処理だと…

「ボタンを押すと 超時間のかかる処理 を実行して、結果を表示する」というケースで試してみます。

まず何も考えず同期処理で書くと、、、

//sync.cs
// ボタンが押されたよ
private void button1_Click(Object sender, EventArgs e)
{
    button.Enabled = false; // 実行中はボタン使えなくする

    // 超時間のかかる計算
    var result = HeavyCalc();

    button.Text = String.Format("result:{0}", result); // 結果を表示する
    button.Enabled = true;
}

// 超時間のかかる計算
private static int HeavyCalc()
{
    System.Threading.Thread.Sleep(10000);
    return 5; // 超時間をかけて 5 を計算したつもり
}

こんな感じ。 動かしてみます。

"anr"

あえなくフリーズ&ANR、当然です。

async/await 化

async/await については、ググればたくさん情報が出てきますが、探した中でもっとも簡単とおもわれる例を紹介します。

さて、先ほどのプログラムを、async/await 構文を使って非同期化してみます。

//async.cs
// ボタンが押されたよ
private async void button1_Click(Object sender, EventArgs e)
{
    button.Enabled = false; // 実行中はボタン使えなくする

    // 超時間のかかる計算
    var result = await HeavyCalcAsync();

    // ここから下は UIスレッド で実行される
    button.Text = String.Format("result:{0}", result); // 結果を表示する
    button.Enabled = true;
}

// 超時間のかかる計算
private static int HeavyCalc()
{
    System.Threading.Thread.Sleep(10000);
    return 5; // 超時間をかけて 5 を計算したつもり
}

// HeavyCalc をラップして非同期で実行
private Task<int> HeavyCalcAsync()
{
    return Task.Run(() => HeavyCalc());
}

変更点を、ソースの下の方から。 まず、HeavyCalc をラップして、 HeavyCalcAsync という関数を作りました。Task クラスを使って非同期で HeavyCalc を実行する処理です。async/await のルールに従って Task クラスを返値にします。メソッド名のおしりに "Async" を付けるのもルールです。

次に、 button1_Click です。メソッドの定義に async キーワードを付けます。このメソッドが非同期である事を示すと共に、メソッド内に await キーワードが含まれる事を意味します。メソッド内に await が無いとエラーになります。(Xamarin Studio でもちゃんとエラーにしてくれました)

最後に、「超時間のかかる計算」の呼び出し。HeavyCalc の代わりに HeavyCalcAsyncawait 付きで記述します。

これで終わり。 「button1_click は、非同期で HeavyCalcAsync を実行し、その終了を待って、その後続処理を UIスレッド で続行する」という意味になりました。

動かしてみます。

"async"

ANR でませんし、ちゃんと計算後に画面が更新されます。

AsyncTask で書くと…

Android で非同期処理と言えば AsyncTask がよく紹介されてますので、一応、それを使うとどうなるのか書いてみます。

まず、AsyncTask を拡張して、HeavyCalc をバックグラウンドで実行する HeavyCalcTask を用意します。 OnPreExecuteOnPostExecute でボタンを無効/有効 にしています。

HeavyCalcTask.cs

// HeavyCalc を非同期で実行する AsyncTask
class HeavyCalcTask : Android.OS.AsyncTask
{
    private readonly Button button;

    public HeavyCalcTask(Button button)
    {
        this.button = button;
    }

    #region implemented abstract members of AsyncTask
    protected override void OnPreExecute()
    {
        button.Enabled = false; // 実行中はボタン使えなくする
    }

    protected override Java.Lang.Object DoInBackground(params Java.Lang.Object[] @params)
    {
        // 超時間のかかる計算
        return HeavyCalc();
    }

    protected override void OnPostExecute(Java.Lang.Object result)
    {
        button.Text = String.Format("result:{0}", result); // 結果を表示する
        button.Enabled = true;
    }
    #endregion
}

使う方は、まあ普通に。

asynctask_execute.cs

// ボタンが押されたよ
private async void button1_Click(Object sender, EventArgs e)
{
    var asyncTask = new HeavyCalcTask(button);
    asyncTask.Execute();
}

クラスを作らなきゃいけないし、AsyncTask の仕方ないところですけど、View に対する処理とロジックが同じクラスに同居しちゃうし、複数の非同期処理を逐次実行できないし、とあまり良い所が見えません。

そもそも Xamarin.iOS などとクロスプラットフォームを考えるならプラットフォーム固有の機能の利用は最小限に留めたいので、Xamarin.Android で AsyncTask を使う意味は「ない」でしょう。 (Xamarin.iOS でも async/await は使えるそうです。)

まとめ

  • 知らない人がコード見ると「これ await してたら ANR 起こりますよね?」とか「これ HeavyCalcAsync が非同期だから、次の行がすぐ実行されちゃいますよね?」とか言われそうw
  • 諸君らが愛してくれた AsyncTask は死んだ
  • async/await が使えるようになるまでは Task.Factory.StartNew で。

参考