当前位置: 动力学知识库 > 问答 > 编程问答 >

c# - Implementing thread safe caching of search results

问题描述:

How the search results caching works

When a user enters a query to search for:

  • The query is split into an array of tokens
  • A unique hash is created for this array of tokens (order tokens alphabetically then MD5). This is the unique identity of the search.
  • Check cache for results based on hash
  • If cache doesn't exist, save results to cache using hash

Problem I'm trying to solve

If a user performs a search that takes say 10 seconds, and they impatiently refresh the page we don't want it to start the query again. This should be locked.

However, if a expensive query is running, we don't want to lock out other users performing less expensive searches.

To solve this, I need multiple locks.

Implementation

This is how I've got it currently implemented:

private static readonly object MasterManualSearchLock = new object();

private static readonly Dictionary<string, object> ManualSearchLocks = new Dictionary<string, object>();

/// <summary>

/// Search the manual

/// </summary>

public static SearchResponse DoSearch(string query, Manual forManual)

{

var tokens = Search.Functions.TokeniseSearchQuery(query);

var tokenHash = Search.Functions.GetUniqueHashOfTokens(tokens);

var cacheIndex = Settings.CachePrefix + "SavedManualSearch_" + tokenHash;

var context = HttpContext.Current;

if (context.Cache[cacheIndex] == null)

{

// Create lock if it doesn't exist

if (!ManualSearchLocks.ContainsKey(tokenHash))

{

lock (MasterManualSearchLock)

{

if (!ManualSearchLocks.ContainsKey(tokenHash))

{

ManualSearchLocks.Add(tokenHash, new object());

}

}

}

lock (ManualSearchLocks[tokenHash])

{

if (context.Cache[cacheIndex] == null)

{

var searchResponse = new SearchResponse(tokens, forManual, query);

context.Cache.Add(cacheIndex, searchResponse, null, DateTime.Now.AddMinutes(Settings.Search.SearchResultsAbsoluteTimeoutMins), Cache.NoSlidingExpiration, CacheItemPriority.BelowNormal, null);

}

ManualSearchLocks.Remove(tokenHash);

}

}

return (SearchResponse)context.Cache[cacheIndex];

}

Questions

  • Is this a sensible implementation?
  • Is this thread safe?
  • Is including the removal of the lock within the lock itself OK?

网友答案:

Your concurrent use of ManualSearchLocks is unsafe, ConcurrentDictionary is a good replacement. No, just reading from the dictionary is not safe because it is not documented to be safe.

I'd put a Lazy<T> into the cache. There might be multiple such lazy instances produced but only one will be materialized. All threads wanting access to a particular key will call Lazy.Value and automatically synchronize. Once one actual "search" will be executed.

Depending on how you access the cache there might be a small race condition that allows for multiple lazy instances to be executed. That is probably not a big deal in your case.

分享给朋友:
您可能感兴趣的文章:
随机阅读: