古い 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 の「オーバーコール」を避けるために結果をキャッシュしていました。私が問題に遭遇したのはここです(私が疑うよりも、非同期プロトコルの理解の欠如と関係があります)。
概要として、私がやりたいことは、最初にキャッシュをチェックし、データが存在しない場合は、キャッシュを再水和し、データを呼び出し元 (Web ページ) に返すことです。これがコードでの私の試みです
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;
}
}
この問題は、Mutex が解放される finally コード ブロックで発生します (全体的なパターンと GetTweetData() メソッドのアプローチについて懸念があります)。
if ((mutex != null) && (iOwnMutex))
{
// The following line throws the error:
// Object synchronization method was called from an
// unsynchronized block of code.
mutex.ReleaseMutex();
}
行をコメントアウトすると、コードは正しく機能しますが、(おそらく) 作成した Mutex を解放する必要があります。私が発見したことから、この問題はミューテックスの作成と解放の間のスレッドの変更に関連しています。
非同期コーディングに関する一般的な知識が不足しているため、a) 使用しているパターンが実行可能かどうか、b) 実行可能である場合、問題にどのように対処するかがわかりません。
アドバイスをいただければ幸いです。