Azureはじめました

Windows Azureで業務システムを組んでみる日記

RedisCacheに非同期でアクセスしようとするとハングアップしてしまう

それなりに大規模なミッションでボトルネックになるデータベースを保護するためにキャッシュ機構を作る必要に迫られて、ならばとAzure Redis Cacheを使ってみた。
Redis Session State Providerは普通に動くし動作も快適。

単純なテストでRedisに読み書きするぶんにも特に問題なし。
ところが、

using StackExchange.Redis;
:


private static  ConnectionMultiplexer connection = ConnectionMultiplexer.Connect("127.0.0.1")
private AnyEntity entity;
private static async Task<T> getOrSet<T>(string CacheKey,  bool forceRefresh, Func<int, T> ext) {
    string key = string.Format("{0}_{1}", cachePrefix,  CacheKey);
    IDatabase cache = connection.GetDatabase();
    string cached = await cache.StringGetAsync(key);
    if (cached != null) {
        try {
            return (T)Jil.JSON.Deserialize(cached, typeof(T));
        }catch{}
    }
    var newData = ext(0);
    cached = Jil.JSON.Serialize(newData);
    cache.StringSet(key, cached);
    return newData;
}
private async Task setNames(bool forceRefresh = false) {
    this.Customers = await getOrSet<List<Customer>>("Customers", forceRefresh, (_hid) => {
        return entity.Customers.OrderBy(data => data.Code).ToList();
    });
}

private async Task setTasks(bool forceRefresh = false) {
    this.Tasks = await getOrSet<List<TeamTask>>("Tasks",forceRefresh, (_hid) => {
        return entity.Tasks.OrderBy(data => data.priority).ToList();
    });
}
private async Task setMembers(bool forceRefresh = false) {
    this.Members = await getOrSet<List<Member>>("Members", forceRefresh, (_hid) => {
        return entity.Members.OrderBy(data => data.member_id).ToList();
    });
}

こんな感じの構造にして、

public async Task loadProperties() {
    await Task.WhenAll(new Task[] { this.setNames(), this.setTasks(), this.setMembers() });
}

こんな感じにいざ非同期メソッドを使って読み書きしようとすると応答が無くなってしまう。

へちょん。

観測してみると

CPUが消費されてるわけじゃなくて単純にGetAsyncの応答が帰ってこないように見える。

\  __  /
_ (m) _  ピコーン
   |ミ|
/ `´  \
  ( ゚∀゚) これはDeadLock
 ノヽノ |
  < <

This is a problem that is brought up repeatedly on the forums and Stack Overflow. I think it’s the most-asked question by async newcomers once they’ve learned the basics.

Don't Block on Async Code

これか。

解決

Preventing the Deadlock

There are two best practices (both covered in my intro post) that avoid this situation:

  1. In your “library” async methods, use ConfigureAwait(false) wherever possible.
  2. Don’t block on Tasks; use async all the way down.
Don't Block on Async Code


あとで追記するかも

余談

One other important point: an ASP.NET request context is not tied to a specific thread (like the UI context is), but it does only allow one thread in at a time. This interesting aspect is not officially documented anywhere AFAIK, but it is mentioned in my MSDN article about SynchronizationContext.

Don't Block on Async Code


('A`)