問題
WebUser
ユーザーがEF経由でログインしたときにオブジェクト をロードするWebポータルに取り組んでいます。WebUser
自明ではないオブジェクト グラフがあり、EF 経由での読み込みには 2 ~ 3 秒かかる場合があります (読み込み時間の最適化は別の問題です)。
WebUser
知覚されるパフォーマンスを向上させるために、ユーザーがシステムにログオンするとすぐに、別のスレッドでをロードしたいと考えています。ただし、現在の試行は、理解できない理由で同期的に実行されます。
コード
static private ConcurrentDictionary<string, WebUser> userCache =
new ConcurrentDictionary<string, WebUser>();
static public void CacheProfile(string userName)
{
if (!userCache.ContainsKey(userName))
{
logger.Debug("In CacheProfile() and there is no profile in cache");
Task bg = GetProfileAsync(userName);
logger.Debug("Done CacheProfile()");
}
}
static public async Task<WebUser> GetProfileAsync(string userName)
{
logger.Debug("GetProfileAsync for " + userName);
await currentlyLoading.NotInSet(userName); // See NOTE 1 below
if (userCache.ContainsKey(userName))
{
logger.Debug("GetProfileAsync from memory cache for " + userName);
return userCache[userName];
}
else
{
currentlyLoading.Add(userName);
logger.Debug("GetProfileAsync from DB for " + userName);
using (MembershipContext ctx = new MembershipContext())
{
ctx.Configuration.LazyLoadingEnabled = false;
ctx.Configuration.ProxyCreationEnabled = false;
ctx.Configuration.AutoDetectChangesEnabled = false;
var wu = GetProfileForUpdate_ExpensiveMethod(ctx, userName);
userCache[userName] = wu;
currentlyLoading.Remove(userName);
return wu;
}
}
}
注 1:currentlyLoading
の静的インスタンスですConcurrentWaitUntil<T>
。これは、最初のリクエストがまだデータベースからロードされている場合に、特定のユーザーのプロファイルに対する 2 番目のリクエストをブロックすることを目的としています。おそらくこれを達成するためのより良い方法がありますか?コード:
public class ConcurrentWaitUntil<T>
{
private HashSet<T> set = new HashSet<T>();
private Dictionary<T, TaskCompletionSource<bool>> completions = new Dictionary<T, TaskCompletionSource<bool>>();
private object locker = new object();
public async Task NotInSet(T item)
{
TaskCompletionSource<bool> completion;
lock (locker)
{
if (!set.Contains(item)) return;
completion = new TaskCompletionSource<bool>();
completions.Add(item, completion);
}
await completion.Task;
}
public void Add(T item)
{
lock (locker)
{
set.Add(item);
}
}
public void Remove(T item)
{
lock (locker)
{
set.Remove(item);
TaskCompletionSource<bool> completion;
bool found = completions.TryGetValue(item, out completion);
if (found)
{
completions.Remove(item);
completion.SetResult(true); // This will allow NotInSet() to complete
}
}
}
}
質問
が完了CacheProfile()
するまで待機しているように見えるのはなぜですか?GetProfileAsync()
補足:ConcurrentDictionary
はうまくスケーリングできず、ASP.Net のキャッシュを使用する必要があることはわかっています。