2014-03-19 1 views
5

이전 HttpHandler (.ashx) TwitterFeed 코드를 WebAPI 응용 프로그램에 이식했습니다. 코드의 핵심은 우수한 Linq2Twitter 패키지 (https://linqtotwitter.codeplex.com/)를 사용합니다. 포트의 일부에는이 구성 요소를 버전 2에서 버전 3으로 업그레이드하는 작업이 포함되어 있습니다.이 버전에서는 새로운 비동기 메서드 호출을 제공합니다. 다음은 기본 컨트롤러 :비동기 메서드에서 뮤텍스를 관리하는 방법

public async Task<IEnumerable<Status>> 
GetTweets(int count, bool includeRetweets, bool excludeReplies) 
{ 
    var auth = new SingleUserAuthorizer 
    { 
     CredentialStore = new SingleUserInMemoryCredentialStore 
     { 
     ConsumerKey  = ConfigurationManager.AppSettings["twitterConsumerKey"], 
     ConsumerSecret = ConfigurationManager.AppSettings["twitterConsumerKeySecret"], 
     AccessToken  = ConfigurationManager.AppSettings["twitterAccessToken"], 
     AccessTokenSecret = ConfigurationManager.AppSettings["twitterAccessTokenSecret"] 
     } 
    }; 

    var ctx = new TwitterContext(auth); 

    var tweets = 
     await 
     (from tweet in ctx.Status 
     where (
      (tweet.Type == StatusType.Home) 
      && (tweet.ExcludeReplies == excludeReplies) 
      && (tweet.IncludeMyRetweet == includeRetweets) 
      && (tweet.Count == count) 
     ) 
     select tweet) 
     .ToListAsync(); 

    return tweets; 
} 

이 잘 작동하지만 이전에, 나는 트위터 API '를 호출을 통해'방지하기 위해 결과를 캐시했다. 여기에 내가 문제가 생겼다. (내가 의심하는 것보다 비동기 프로토콜에 대한 나의 이해가 부족하다.)

개요에서 데이터를 찾지 못하면 캐시를 먼저 확인한 다음 캐시를 다시 확보하고 호출자 (웹 페이지)로 데이터를 반환합니다.

if ((mutex != null) && (iOwnMutex)) 
{ 
    // The following line throws the error: 
    // Object synchronization method was called from an 
    // unsynchronized block of code. 
    mutex.ReleaseMutex(); 
} 
: 여기에 코드 (필자는 GetTweetData() 메소드의 전체적인 패턴과 접근 방식에 대한 우려가 있지만) 문제는 뮤텍스가 해제되고 마지막 코드 블록에서 발생

public class TwitterController : ApiController { 

    private const string CacheKey = "TwitterFeed"; 

    public async Task<IEnumerable<Status>> 
    GetTweets(int count, bool includeRetweets, bool excludeReplies) 
    { 
     var context = System.Web.HttpContext.Current; 
     var tweets = await GetTweetData(context, count, includeRetweets, excludeReplies); 
     return tweets; 
    } 

    private async Task<IEnumerable<Status>> 
    GetTweetData(HttpContext context, int count, bool includeRetweets, bool excludeReplies) 
    { 
     var cache = context.Cache; 
     Mutex mutex = null; 
     bool iOwnMutex = false; 
     IEnumerable<Status> data = (IEnumerable<Status>)cache[CacheKey]; 

     // Start check to see if available on cache 
     if (data == null) 
     { 
     try 
     { 
      // Lock base on resource key 
      mutex = new Mutex(true, CacheKey); 

      // Wait until it is safe to enter (someone else might already be 
      // doing this), but also add 30 seconds max. 
      iOwnMutex = mutex.WaitOne(30000); 

      // Now let's see if some one else has added it... 
      data = (IEnumerable<Status>)cache[CacheKey]; 

      // They did, so send it... 
      if (data != null) 
      { 
       return data; 
      } 

      if (iOwnMutex) 
      { 
       // Still not there, so now is the time to look for it! 
       data = await CallTwitterApi(count, includeRetweets, excludeReplies); 

       cache.Remove(CacheKey); 
       cache.Add(CacheKey, data, null, GetTwitterExpiryDate(), 
        TimeSpan.Zero, CacheItemPriority.Normal, null); 
      } 
     } 
     finally 
     { 
      // Release the Mutex. 
      if ((mutex != null) && (iOwnMutex)) 
      { 
       // The following line throws the error: 
       // Object synchronization method was called from an 
       // unsynchronized block of code. 
       mutex.ReleaseMutex(); 
      } 
     } 
     } 

     return data; 
    } 

    private DateTime GetTwitterExpiryDate() 
    { 
     string szExpiry = ConfigurationManager.AppSettings["twitterCacheExpiry"]; 
     int expiry = Int32.Parse(szExpiry); 
     return DateTime.Now.AddMinutes(expiry); 
    } 

    private async Task<IEnumerable<Status>> 
    CallTwitterApi(int count, bool includeRetweets, bool excludeReplies) 
    { 
     var auth = new SingleUserAuthorizer 
     { 
     CredentialStore = new SingleUserInMemoryCredentialStore 
     { 
      ConsumerKey = ConfigurationManager.AppSettings["twitterConsumerKey"], 
      ConsumerSecret = ConfigurationManager.AppSettings["twitterConsumerKeySecret"], 
      AccessToken = ConfigurationManager.AppSettings["twitterAccessToken"], 
      AccessTokenSecret = ConfigurationManager.AppSettings["twitterAccessTokenSecret"] 
     } 
     }; 

     var ctx = new TwitterContext(auth); 

     var tweets = 
     await 
     (from tweet in ctx.Status 
      where (
      (tweet.Type == StatusType.Home) 
      && (tweet.ExcludeReplies == excludeReplies) 
      && (tweet.IncludeMyRetweet == includeRetweets) 
      && (tweet.Count == count) 
      && (tweet.RetweetCount < 1) 
     ) 
      select tweet) 
     .ToListAsync(); 

     return tweets; 
    } 

} 

에서 내 시도이다

줄을 주석 처리하면 코드가 제대로 작동하지만 (실제로는) 내가 만든 뮤텍스를 릴리스해야합니다. 내가 발견 한 바에 따르면,이 문제는 뮤텍스를 생성하고 릴리스하는 사이에 변경되는 스레드와 관련이 있습니다.

비동기 코딩에 관한 일반적인 지식이 부족하기 때문에, 내가 사용하고있는 패턴이 실행 가능하고 b) 그것이 맞다면 어떻게 문제를 해결할 수 있을지 확신 할 수 없습니다.

조언을 주시면 감사하겠습니다.

답변

4

그런 뮤텍스를 사용하면 효과가 없습니다. 예를 들어 Mutex은 thread-affine이므로 async 코드와 함께 사용할 수 없습니다.

  • Cache이 스레드, 그래서 어쨌든 뮤텍스 (또는 다른 보호) 필요가 없습니다 :

    다른 문제는 내가 나타났습니다.

  • 비동기 메서드는 Task-based Asynchronous Pattern 뒤에 와야합니다. 그냥 메모리 내 캐시가있을 때, 다음 작업보다는 결과 데이터를 캐시 :

캐싱에 대한 한 가지 중요한 팁이있다. 덧붙여 말하자면, HttpContext.Cache이 사용하기에 가장 좋은 캐시인지는 의문입니다.하지만 비동기 코드가 캐싱 패턴을 변경하는 방법에 대한 질문이 있으니 그대로 두겠습니다.

그래서,이 같은 것을 권하고 싶습니다 : 작은 가능성이있다

private const string CacheKey = "TwitterFeed"; 

public Task<IEnumerable<Status>> GetTweetsAsync(int count, bool includeRetweets, bool excludeReplies) 
{ 
    var context = System.Web.HttpContext.Current; 
    return GetTweetDataAsync(context, count, includeRetweets, excludeReplies); 
} 

private Task<IEnumerable<Status>> GetTweetDataAsync(HttpContext context, int count, bool includeRetweets, bool excludeReplies) 
{ 
    var cache = context.Cache; 
    Task<IEnumerable<Status>> data = cache[CacheKey] as Task<IEnumerable<Status>>; 
    if (data != null) 
    return data; 
    data = CallTwitterApiAsync(count, includeRetweets, excludeReplies); 
    cache.Insert(CacheKey, data, null, GetTwitterExpiryDate(), TimeSpan.Zero); 
    return data; 
} 

private async Task<IEnumerable<Status>> CallTwitterApiAsync(int count, bool includeRetweets, bool excludeReplies) 
{ 
    ... 
} 

(두 개의 서로 다른 세션에서) 두 개의 서로 다른 요청이 피드는 것을, 똑같은 시간에 같은 트위터 피드를 요청하는 경우 그 두 번 요청됩니다. 그러나 나는 그것 위에서 잠을 자지 않을 것이다.

+0

스티븐 감사합니다. 매우 유용합니다. 뮤텍스를 구현하려했던 이유는 두 세션의 두 가지 트위터 요청을 피하기 위해서였습니다.트위터 피드는 웹 사이트의 모든 페이지에 표시되므로 피드가 반환하는 데 5 초가 걸리면 동시 요청 수가 2 회일 확률은 상당히 높지만 사용자의 입장을 고려합니다. 다시 귀하의 상세한 설명과 예제를 주셔서 감사합니다. – Neilski

+0

작업을 캐시하면 트위터 피드가 두 번 요청 될 확률이 매우 낮습니다. 다운로드가 * 시작된 순간 * 즉시 * 작업이 캐시에 추가되기 때문입니다. –

+0

그것은 실제로 환상적으로 영리합니다. 나는 그것이 다운로드되기 전에 다른 프로세스가 캐시에 대한 요청을하는 경우에도 Task를 캐싱하기 때문에 Task가 응답 할 때까지 기다리고 있다고 가정합니다. 그것이 가능하다는 것을 이해하기가 어렵습니다! – Neilski