Xamarin Blog で "Alpha版だけど async/await 使えるようになったよー" との事だったのでさっそく試してみました。
以前から、"Good news – we plan to release full support for async/await in all our products in April of 2013." と言われていたので予定どおりですかね。
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、当然です。
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
の代わりに HeavyCalcAsync
を await
付きで記述します。
これで終わり。 「button1_click は、非同期で HeavyCalcAsync を実行し、その終了を待って、その後続処理を UIスレッド で続行する」という意味になりました。
動かしてみます。
ANR でませんし、ちゃんと計算後に画面が更新されます。
Android で非同期処理と言えば AsyncTask
がよく紹介されてますので、一応、それを使うとどうなるのか書いてみます。
まず、AsyncTask を拡張して、HeavyCalc をバックグラウンドで実行する HeavyCalcTask
を用意します。
OnPreExecute
と OnPostExecute
でボタンを無効/有効 にしています。
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 は使えるそうです。)
AsyncTask
は死んだTask.Factory.StartNew
で。