2013年4月2日火曜日

wpf : MemoryCacheを使ってBitmapSourceをキャッシュ

wpfで画像をロードするとキャッシュされるらしいですね。 しかし、「どのようにキャッシュされるか?」とか「どれだけキャッシュされるか?」とか細かいキャッシュの動作について不明なことが多いです。 まぁ、どっかに書いてあるのかもしれませんが、それを探すより自前でキャッシュを制御した方が早い気がします。

というわけで前の投稿のコードを元にを読み込むメソッドを作ってみました。 読み込んだBitmapSourceはMemoryCacheを使ってキャッシュします。

// Util.cs
using System.Diagnostics;
using System.IO;
using System.Runtime.Caching;
using System.Windows.Media.Imaging;

namespace 適当なネームスペース
{
    class Util
    {
        // シングルスレッド用
        public static BitmapSource LoadBitmap(string path)
        {
            var cache = MemoryCache.Default;
            var bmp = (BitmapSource)cache.Get(path);

            if (bmp == null)
            {
                using (Stream stream = new FileStream(
                        path,
                        FileMode.Open,
                        FileAccess.Read,
                        FileShare.ReadWrite | FileShare.Delete
                    ))
                {
                    // ロックしないように指定したstreamを使用する。
                    BitmapDecoder decoder = BitmapDecoder.Create(
                        stream,
                        BitmapCreateOptions.None,
                        BitmapCacheOption.None // キャッシュはMemoryCacheのみ
                    );
                    bmp = new WriteableBitmap(decoder.Frames[0]);
                }
                bmp.Freeze();
                cache.Add(path, bmp, new CacheItemPolicy()
                {
                    RemovedCallback = (arg) =>
                    {
                        Debug.WriteLine("cache removed ... " + path);
                    }
                });
            }

            return bmp;
        }
    }
}

キャッシュのキーにはファイルのパスを使用しています。 読み込みを成功したときしかキャッシュに登録していないので変なキーになることはあまりないでしょう。 パスにシンボリックリンクが含まれているときだけ注意かな? まぁそういうチェックは呼び出し側がするって事で。

本来はCacheItemPolicyにキャッシュエントリの有効期限とかを設定すべきなんですが、このサンプルではそういうのは設定していません。 アプリケーションが動いている間、キャッシュ容量がいっぱいになるまで読んだ画像は抱えたままです。 アプリケーション構成ファイルでキャッシュの最大サイズを制限するという前提。 デフォルトだとosの使用領域とか全く考慮せずに全メモリ使おうとする設定になっているような? 恐ろしいですね。

ということでこのまま使う場合は前の投稿を参考にアプリケーション構成ファイルを書いてください。 「アプリケーション構成ファイルなんて嫌だ!」って場合も前の投稿を参考にデフォルトじゃないMemoryCacheを作りましょう。

というか、ちゃんとエントリを消す条件をCacheItemPolicyに書くとか、アプリケーションがキャッシュを残す、消すの判断をするとかした方がいいですね。