atsukanrockのブログ

Microsoft系技術を中心にぼちぼち更新します

Windows 8.1でのasync voidメソッドに対する例外処理の改善

※この記事はException handling improvements for async void methods in Windows 8.1のざっくり和訳です。

ざっくり言うと

async voidなメソッド内で例外が起こると:

  • Windows 8では‥例外は補足不能。アプリは死ぬ
  • Windows 8.1だと‥Application.UnhandledExceptionイベントハンドラーで例外を補足可能。e.Handled = true;でアプリが死なないようにもできる!

というお話。async voidはベストプラクティス的には禁忌なんですけど、Commandとかイベントハンドラーでめっちゃ使うので、ほっこり嬉しい改善です。

補足すると

ただし「Windows 8.1以降のWindowsストアアプリ開発では、未処理例外が全部Application.UnhandledExceptionイベント飛んでくる」わけではないので注意が必要です。例えば次のようなコード(意味ないけど)があったら飛んできません。

private void ButtonOnClick(object sender, RoutedEventArgs e)
{
    Task.Run(() => { throw new InvalidOperationException(); });
}

ちゃんとawaitしてあげましょう。普通にasync/awaitを使って書いてれば飛んできます。

private async void ButtonOnClick(object sender, RoutedEventArgs e)
{
    await Task.Run(() => { throw new InvalidOperationException(); });
}

ConfigureAwait(false)してても飛んできます。

private async void ButtonOnClick(object sender, RoutedEventArgs e)
{
    await Task.Run(() => { throw new InvalidOperationException(); }).ConfigureAwait(false);
}

Windowsストアアプリで同梱のPDFファイルを開く

WindowsストアアプリにPDFファイルを同梱して、それを当該アプリから開くやり方です。気合の入ったREADMEをPDF化してアプリに同梱して、開くボタンを付けるなんて良いですね!それでWindowsストアの審査に通るかどうか知らんけど。

PDFファイルを同梱するには

PDFファイルをプロジェクトに含めて、プロパティのビルド アクションを「コンテンツ」にします。

f:id:atsukanrock:20140130144440p:plain

アプリから開くには

こんな感じのコードで開けます。

private async void ExecuteOpenPdfCommand()
{
    // プロジェクトフォルダー直下の Assets フォルダー内にある Sample.pdf を表示する。
    var uri = new Uri("ms-appx:///Assets/Sample.pdf");
    var file = await StorageFile.GetFileFromApplicationUriAsync(uri);

    // PDF ファイルに関連付けられたアプリが起動して、画面の半分を使って表示される。
    // デスクトップアプリが関連付けられていると…。
    await Launcher.LaunchFileAsync(file, new LauncherOptions
    {
        DesiredRemainingView = ViewSizePreference.UseHalf
    });
}

こんな感じ。*1

f:id:atsukanrock:20140130151408p:plain

ポイントは:

  • URIの指定方法
  • StorageFileの取得方法
  • Launcherクラスを使用すること

応用

ちょっと工夫すれば次のようなことができます:

*1:左側に起動元アプリが表示されています。

勉強会「『プログラミング .NET Framework 第4版 』座談会」で発表しました

はてなダイアリーが許されるのは小学生までだよねー」」「キャハハハハ」という声に押されてはてなブログに移住してみようと思います。せっかくだからMarkdownの練習もしたい。

「『プログラミング .NET Framework 第4版 』座談会」で発表してきました。ということでスライドを公開。内容は、グローバル対応アプリの日時処理で知っておくべきことと注意点をまとめたものです。アウトラインは次の通り:

「Silverlightを囲む会in東京#6」に登壇します

Silverlightを囲む会in東京#6に登壇します。

カスタムコントロールの作り方でも紹介しようかなぁ、などと思ってます。テーマが「Silverlight5新機能とSilverlightの基礎」なのにSilverlight 5のお話を入れられそうになく、そのうえ周りの講師がみんなマイクロソフトの方だったりMVPの方だったりで、とっても恐縮しております。

プレゼン内容、まだ勉強すらできてません。これから調べます。準備間に合うんだろうか。。。

Windowsフォームアプリケーションでリソースリークしないために(2)

連載記事にするつもりが、第1回のみ公開して第2回は下書きに放置していた。第2回が書きかけだからだが、当時のことをほぼ完全に忘れているので書き切れそうにない。なのでエイヤッと公開しておく。このあとControl.InvokeやBeginInvokeに関する話(この辺とか)を書きたかったような気がするのだが…。

画像非同期読み込みコントロールについて

今回特に多くリソースリークしたのが画像非同期読み込みコントロールだ。本エントリでも何度も触れる(はずな)ので、その概要を説明しておく。

当該コントロール(以降「AsyncPictureBox」とする)はPictureBoxをラップしたユーザーコントロール*1。DBにBLOB値として格納されている画像を読み込むpublicメソッド(以降「BeginLoadメソッド」とする)を公開している。画像をDBから読み込むので当然、パフォーマンス上の懸念がある。そこで画像読み込みは、非メインスレッドで非同期に行う。これにより画像の読み込みが完了しなくても操作が可能になるので、ユーザーにとっての体感速度が向上する。

画像を表示する枠が表示されているのに画像の非同期読み込みが完了するまでそこに何も表示されないのは、何だか寂しい。よってその間、読み込み中だと分かるGIFアニメーション画像(こういうの)を表示する。

DBから画像を読み込もうとしたら、存在しないことがあり得る。DB内の画像はいつ削除されるか分からない(そういうシステムだ)ためだ。AsyncPictureBoxはそういった場合、画像が存在しないことを表す画像(以降「NoImage画像」とする)を表示する。その画像は静的なので、アプリケーションにリソースとして埋め込む。

最後にAsyncPictureBoxは、場合によっては同時に500個程度も配置される。

動的コントロール破棄時は、Remove/Clearの後にDispose

Visual Studioのデザイナだけでコントロールの配置が事足りることはあまりない。大抵の場合データの状態などに応じて配置すべきコントロールの数などが変化するため、デザイナでは事足りない。そのような場合一般的に、コントロールインスタンスを生成するコードを自分で書くが、そうやって生成したコントロールのことを動的コントロールと言う。

動的コントロールのライフサイクル*2はしばしば、その親コントロールのライフサイクルより短い。よくあるのが例えば、PanelがFormと同じライフサイクルで、そのPanel内に動的コントロールを配置、あるボタンが押されるとPanel内のコントロールを全てクリアし、またそのPanel内に動的コントロールを配置、といった感じだ。

Windowsフォームアプリケーションプログラミングの基礎で、コントロールの破棄時にはDisposeしなければならない。*3そこで次のようなユーティリティメソッド*4が書ける。

public static void ClearControls(this Control control)
{
    if (control == null)
    {
        throw new ArgumentNullException("control");
    }
    var controls = control.Controls.Cast<Control>().ToArray();
    control.Controls.Clear();
    foreach (var ctrl in controls)
    {
        ctrl.Dispose();
    }
}

ここでポイントなのが、DisposeをClearより後に行っている点だ。そのためにClearの前にControlsをバックアップしておくという、少々直感的でないコードになっている。なぜそうするかというと、Dispose済みのオブジェクトへの参照がないようにする、という.NETプログラミングの鉄則に則るためだ。1つのメソッド内の話なので、マルチスレッド絡みでもない限り問題が起きそうにはないが。

と、ここまで書いて説明の順番を誤ったと気づいた。この場合はDispose→Clearでも問題が起きたことがないのだった。もしかするとウィンドウハンドルがリークするのではないかと思うが、そこまでの確認をしていない。ただこの順序が非常に重要なケースがある。それを次に説明する。

ImageListをたくさん配置してはいけない

AsyncPictureBoxの初期の実装では、PictureBox以外にもう1つ、ImageListを配置していた。そしてVisual Studioのデザイナで、そのImageListにNoImage画像を格納しておいた。その結果、あまりに多くメモリを消費することになってしまった。まずはその理由を説明する。

Visual StudioのデザイナでImageListを配置して画像を読み込ませると、画像のバイナリデータが文字列化*5されてRESXファイルに保存される。アプリケーションの実行時には、当該ImageListを配置したコントロールのコンストラクタ内で、RESXの文字列から実際の画像のバイナリデータに変換される。

従ってAsyncPictureBoxのコンストラクタ内では、実際にNoImage画像が表示されるかどうかによらずNoImage画像のバイナリデータがメモリ内に配置されることになる。これはあまりにメモリの無駄だ。

ではどうするかというと、アセンブリに埋め込んだ画像リソースを使う。なぜ画像リソースにすべきなのか。理由は簡単で、画像リソースなら必要になった時にだけメモリ内に配置することができる(自然にそうなる)からだ。

Visual Studioを使えば画像リソースをアセンブリに埋め込むのは直感的で簡単だ。DOBON.NETさんが素晴らしい記事を書いてくれている。

*1:今思うとPictureBoxから派生したコントロールの方が良かったかもしれない。

*2:インスタンスが生成されてから破棄(DisposeおよびGC)されるまでの期間

*3:正確には.NETプログラミングの基礎かもしれない。コントロールがIDisposableインターフェイスを実装しているので。

*4:拡張メソッドにした

*5:おそらくbase64か

Nullableと3値論理

.NETのNullableの比較について記憶できないのでメモ。

nullと非nullの比較

int? value1 = null;
int? value2 = 10;

だと

結果
value1 < value2 false
value1 <= value2 false
value1 == value2 false
value1 != value2 true
value1 > value2 false
value1 >= value2 false

nullとnullの比較

int? value1 = null;
int? value2 = null;

だと

結果
value1 < value2 false
value1 <= value2 false
value1 == value2 true
value1 != value2 false
value1 > value2 false
value1 >= value2 false

考察

どうやら次のような感じか。

  • 「==」や「!=」での比較は、SQLにおけるIS NULL的にちゃんと行われる。
  • 「>」や「<」などによる大小の比較は、nullが混じった時点で常にfalseを返す。

感想

試す前はSQL3値論理的なノリで、nullが混じった時点で全部falseかと思ったが、実際には違った。書いていて思ったけど3値論理関係ないですね。単なるNULLの特性の話ですね。

間違いなく言えることは、このエントリーの内容に依存したコードとか書いちゃダメ!!ちゃんとnullチェックしましょう。