まぐらぼ

Unity/Android、Microsoft系のWPFをやってます。

async/awaitの罠

はまったのでメモります。

Windows8ストアアプリ基盤のWinRTは非同期設計のため、C#5.0のasync/awaitと一緒に使う事が多いです。WinRT-APIを待つためawaitを使うと関数先頭にasyncをつける必要あります。このとき何も考えずに使いがちですがasyncをつけると非同期実行となるため、状況によってはまります。

今回はツイッターAPIの処理ではまりました。

// 認証処理
async Task<int> test_tw() 
{
    var consumerKey = CONSUMER_KEY;
    var consumerKeySecret = CONSUMER_SECRET;

	... 省略 ...

    // APIにアクセスするためのHttpClientを取得する
    HttpClient client = OAuthUtility.CreateOAuthClient(
        consumerKey,
        consumerKeySecret,
        accessToken.Token);

	//投稿処理
    string status = "hoge2: for test api v1.1";
    var content = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("status", status) });
    var response = await twi_manager.client.PostAsync("https://api.twitter.com/1.1/statuses/update.json", content);

    return 0;

}

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    test_tw();//認証と投稿
}

test_tw()に認証と投稿処理を書いた場合は問題ありません。次に投稿処理を別関数にして一般化を考えたときに落とし穴があります。

async Task<int> test_tw() 
{
    // 認証処理
    return 0;
}

//投稿
async void test_post_update()
{
    //投稿
}

さらに呼出し側を次のように変更します。

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    test_tw();		 //認証
    test_post_update();//投稿
}

これで問題なさそうな感じがしますが動作しません。なぜならば、test_tw()が非同期で動作するため、test_tw()の終了を待たずにtest_post_update()が動くためです。サーバ側での認証処理が未完了で投稿を行おうとするので投稿が蹴られてエラーが発生します。test_tw()の最後で認証処理終了フラグをONにして、test_post_update()の先頭でフラグをチェックするなどして挙動を確認できます。

ただしくはawaitでtest_tw()の完了を待つ必要があります。test_post_update()で不慣れなAPI使っていると自分の使い方が間違っているのかな?と考え、意識がそちらに向かってしまいます。

async protected override void OnNavigatedTo(NavigationEventArgs e)
{
    await test_tw();			  //認証
    test_post_update(_twimanager);//投稿
}