0

問題

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 のキャッシュを使用する必要があることはわかっています。

4

3 に答える 3

0

CacheProfile メソッドも非同期である必要があります。そうしないと、GetProfileAsync から結果を取得するまで終了しません。

火が欲しいだけで忘れてしまう場合は、これを使用してください:

static void ReadAndCache()
{
    // retrieve and place in cache
}

static public void GetProfileAsync(string userName)
{
    Task.Run(() => ReadAndCache());
}
于 2013-10-22T01:42:06.843 に答える