问题
我正在开发一个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>
. 目的是如果第一个请求仍在从数据库加载,则阻止对给定用户配置文件的第二个请求。也许有更好的方法来实现这一点?代码:
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 的缓存。