[メモ]従来のコールバック関数をC#5.0でサポートされているawaitフォーマットに変換する方法

4370 ワード

C#5.0はコンパイラがサポートするasyncとawaitキーワードを導入し、開発者に同期思想を用いて非同期コードを書く便利さを提供した.
しかし、一部の従来の関数は非同期コールバック実装のみを提供しており、awaitの利便性を享受できるようにパッケージするにはどうすればいいのでしょうか.
例えば、Windows Phone SDKが提供するContacts.SearchAsync()関数はDelegateでContactsを処理する必要がある.SearchAsyncイベント.
Windows Phone 8で連絡先を検索するコードは以下の通りです.
void Contacts_SearchCompleted(object sender, ContactsSearchEventArgs e) {
    try {
        Debug.WriteLine(e.Results.Count());
    }
    catch (Exception ex) {
        Debug.WriteLine(ex.ToString());
    }
}

private void btnSearchContacts_Click(object sender, RoutedEventArgs e) {
    Contacts cons = new Contacts();
    cons.SearchCompleted += new EventHandler<ContactsSearchEventArgs>(Contacts_SearchCompleted);
    cons.SearchAsync("", FilterKind.None, "Contacts Test");
}

このようなコールバック方式が悪いというわけではありませんが、次のようなawaitを使う方式で呼び出すともっといいと思います.
async private Task<IEnumerable<Contact>> SearchContacts(string filter, FilterKind filterKind, Object state) {
    return await Task<IEnumerable<Contact>>.Run(
        () => {
            Debug.WriteLine("SearchContacts begin");

            var signal = new ManualResetEvent(false);
            IEnumerable<Contact> ret = null;

            Contacts cons = new Contacts();
            cons.SearchCompleted += (o, e) => {
                Debug.WriteLine("SearchContacts complete");
                ret = e.Results;
                signal.Set();
            };
            cons.SearchAsync(filter, filterKind, state);
            signal.WaitOne();
            signal.Dispose();
            Debug.WriteLine("SearchContacts end");
            return ret;
        }
        );
}

async private void btnSearchContactsAwait_Click(object sender, RoutedEventArgs e) {
    IEnumerable<Contact> cons = await SearchContacts("", FilterKind.None, "Contacts Test");
    Debug.WriteLine(cons.Count());
}

コードをTaskにパッケージし、ManualResetEventでコードフローを制御し、超キックアスを使用します.