2014-03-19 87 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; 
} 

這工作正常,但以前,我緩存了結果,以避免'過度調用'的Twitter API。在這裏,我遇到了一個問題(更多的是因爲我對異步協議的理解不夠,而不是我懷疑的其他任何東西)。

總的來說,我想要做的是首先檢查緩存,如果數據不存在,那麼補充水分緩存和數據返回給調用者(網頁)。這裏是我的代碼

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; 
    } 

} 

在最後代碼塊中的互斥被釋放時出現問題(雖然我對GetTweetData()方法的整體格局和方式問題)嘗試:

if ((mutex != null) && (iOwnMutex)) 
{ 
    // The following line throws the error: 
    // Object synchronization method was called from an 
    // unsynchronized block of code. 
    mutex.ReleaseMutex(); 
} 

如果我註釋掉,代碼工作正常,但(我認爲)我應該釋放互斥鎖已經創造了它。從我發現的情況來看,這個問題與創建和釋放互斥鎖之間的線程改變有關。

因爲我缺乏異步編碼一般知識的,我不知道一),如果我使用的模式是可行的和b)如果是這樣,我怎麼解決這個問題。

任何意見將不勝感激。

回答

4

使用像這樣的互斥體行不通。首先,Mutex是線程仿射的,因此它不能與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) 
{ 
    ... 
} 

有一個小的可能性,如果兩個不同的請求(來自兩個不同的會話)請在完全相同的時間相同Twitter的飼料,該飼料將被要求兩次。但我不會因此而失眠。

+0

謝謝斯蒂芬,非常有幫助。我試圖實現互斥鎖的原因是爲了避免來自兩個會話的兩個Twitter請求。Twitter資訊提供顯示在網站的每個頁面上,所以我推斷如果Feed需要5秒鐘的時間才能返回,那麼兩次同時請求的機率相當高,但我明白你的觀點。再次感謝您的詳細解釋和示例。 – Neilski

+0

如果你緩存任務,那麼Twitter提要將被請求兩次的概率非常低。這是因爲任務在下載開始時立即添加到緩存中,而不是在完成時添加到緩存中。 –

+0

這實際上非常巧妙。我假設,因爲它正在緩存一個任務,即使另一個進程在緩存下載之前向緩存發出請求,那麼它只是等待任務作出響應?我發現很難理解這甚至是可能的! – Neilski