2013-10-22 2 views
0

나는 사용자가 EF를 통해 로그인 할 때 WebUser 객체를로드하는 웹 포털에서 일하고 있어요 문제점작업은 동 기적으로

실행 컨트롤러 액션의 시작. 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 : currentlyLoadingConcurrentWaitUntil<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의 캐시를 사용해야한다는 것을 알고 있습니다.

답변

2

왜 CacheProfile()은 GetProfileAsync()가 완료 될 때까지 기다리는 것처럼 보입니까? 당신의 GetProfileAsync 먼저 몇 가지 비동기 작업을 수행 한 후동기 데이터베이스 호출 및 을하고 같은

는 소리가 난다.

EF를 사용하고 있으므로 EF6으로 업그레이드하고 asynchronous queries을 사용하여이 문제를 해결할 수 있습니다.

또는 Task.Run으로 설정하면 효과적 일 수 있지만 확장성에 해를 끼치기 때문에 서버 측에서 권장하지 않습니다.보조 노트에

, 나는 그들이 작업 대신 결과를 캐시하도록 메모리 비동기 캐시를 구성하는 것을 선호하므로이 같은 : 초기 로그인에 그런

static private ConcurrentDictionary<string, Task<WebUser>> userCache = new ConcurrentDictionary<string, Task<WebUser>>(); 

static public Task<WebUser> GetProfileAsync(string userName) 
{ 
    return userCache.GetOrAdd(userName, _ => 
    { 
    logger.Debug("In GetProfileAsync() and there is no profile in cache"); 
    return LoadProfileAsync(userName); 
    }); 
} 

static private async Task<WebUser> LoadProfileAsync(string userName) 
{ 
    // Load it from DB using EF6 async queries. 
    // Don't block other callers. 

    logger.Debug("Loading from DB complete"); 
} 

, 당신 GetProfileAsync을 호출하고 그 결과를 무시하면됩니다.

+0

'GetProfileAsync()'의 완전한 코드를 추가했습니다. 첫 번째 메소드 호출은'await'입니다. 현재 라인 목록과 사용자 이름을 프로파일 목록에 추가하는 선 사이에 잠재적 경쟁 조건이 있음을 알고 있습니다. 두 번째 요청은 실제 클릭에 의해 트리거되므로 실제로이 경우에는 발생하지 않습니다. 이제는 결과가 아닌 작업을 캐싱하기위한 패턴을 찾고 있습니다. –

+0

사이드 노트에서 이전에는 결코 ' _은 유효한 매개 변수 이름입니다. –

+0

결과가 아닌 작업을 캐싱 할 때 메모리 내 캐시를 업데이트하는 방법은 무엇입니까? 메모리 내 표시를 업데이트하고 변경 내용을 DB. 이것은 http://entityframework.codeplex.com/workitem/864 –

0

CacheProfile 메소드도 비동기 적이어야합니다.

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

static public void GetProfileAsync(string userName) 
{ 
    Task.Run(() => ReadAndCache()); 
} 
+0

특히 CacheProfile이 비동기 적이기를 원하지 않습니다. 컨트롤러 프로 시저가 비동기이기를 요구하기 때문입니다. 나는 그 행동을 찾고 있지 않다. 컨트롤러가 비동기 컨트롤러를 생성하지 않고'Task'를 시작한 후 * 리턴하고 완료하기를 원합니다. –

+0

@EricJ. 작업에서 반환 한 데이터를 사용 하시겠습니까? 그렇지 않다면 그냥 발사하고 잊어 버리십시오. 작업 결과를 얻으려면 작업이 완료 될 때까지 기다려야합니다. 그 동안 기다리면 다른 작업을 수행 할 수 있습니다. – evhen14

+0

컨트롤러 동작에서 데이터를 사용할 필요가 없습니다. 나는 "화재와 잊기"를 시도하고 있지만 컨트롤러 동작이 실제로 시작될 때까지 블록을 완료한다는 것을 알게됩니다. 나는 그것이 왜 차단하고 있는지 전혀 모른다. –

0

을 당신이 behavior.Please 다음

보고 원하는 얻을 것이다 await를 사용하여 생각 : 당신은 그냥 불을 원하고 이것을 사용 잊어 버린 경우가 GetProfileAsync

발생할 때까지 그렇지 않으면 끝나지 않을 것

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) 
{ 
    //load you data here. 
    // will create a new thread and start task and return to the calling method. 
    return await Task.Run(() => { 
       //your code which fetches data goes here 
       //which returns a "WebUser" object 
      }); 
    logger.Debug("Loading from DB complete"); 
} 
관련 문제