Doing locking in ASP.NET correctly - c#

I have an ASP.NET site with a fairly slow search function, and I want to improve performance by adding the results to the cache for one hour using the query as the cache-key:
using System;
using System.Web;
using System.Web.Caching;
public class Search
{
private static object _cacheLock = new object();
public static string DoSearch(string query)
{
string results = "";
if (HttpContext.Current.Cache[query] == null)
{
lock (_cacheLock)
{
if (HttpContext.Current.Cache[query] == null)
{
results = GetResultsFromSlowDb(query);
HttpContext.Current.Cache.Add(query, results, null, DateTime.Now.AddHours(1), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
}
else
{
results = HttpContext.Current.Cache[query].ToString();
}
}
}
else
{
results = HttpContext.Current.Cache[query].ToString();
}
return results;
}
private static string GetResultsFromSlowDb(string query)
{
return "Hello World!";
}
}
Let’s say visitor A does a search. The cache is empty, the lock is set and the result is requested from the database. Now visitor B comes along with a different search: Won’t visitor B have to wait by the lock until visitor A’s search has completed? What I really wanted was for B to call the database immediately, because the results will be different and the database can handle multiple requests – I just don’t want to repeat expensive unnecessary queries.
What would be the correct approach for this scenario?

Unless you're absolutely certain that it's critical to have no redundant queries then I would avoid locking altogether. The ASP.NET cache is inherently thread-safe, so the only drawback to the following code is that you might temporarily see a few redundant queries racing each other when their associated cache entry expires:
public static string DoSearch(string query)
{
var results = (string)HttpContext.Current.Cache[query];
if (results == null)
{
results = GetResultsFromSlowDb(query);
HttpContext.Current.Cache.Insert(query, results, null,
DateTime.Now.AddHours(1), Cache.NoSlidingExpiration);
}
return results;
}
If you decide that you really must avoid all redundant queries then you could use a set of more granular locks, one lock per query:
public static string DoSearch(string query)
{
var results = (string)HttpContext.Current.Cache[query];
if (results == null)
{
object miniLock = _miniLocks.GetOrAdd(query, k => new object());
lock (miniLock)
{
results = (string)HttpContext.Current.Cache[query];
if (results == null)
{
results = GetResultsFromSlowDb(query);
HttpContext.Current.Cache.Insert(query, results, null,
DateTime.Now.AddHours(1), Cache.NoSlidingExpiration);
}
object temp;
if (_miniLocks.TryGetValue(query, out temp) && (temp == miniLock))
_miniLocks.TryRemove(query);
}
}
return results;
}
private static readonly ConcurrentDictionary<string, object> _miniLocks =
new ConcurrentDictionary<string, object>();

Your code has a potential race condition:
if (HttpContext.Current.Cache[query] == null)
{
...
}
else
{
// When you get here, another thread may have removed the item from the cache
// so this may still return null.
results = HttpContext.Current.Cache[query].ToString();
}
In general I wouldn't use locking, and would do it as follows to avoid the race condition:
results = HttpContext.Current.Cache[query];
if (results == null)
{
results = GetResultsFromSomewhere();
HttpContext.Current.Cache.Add(query, results,...);
}
return results;
In the above case, multiple threads might attempt to load data if they detect a cache miss at about the same time. In practice this is likely to be rare, and in most cases unimportant, because the data they load will be equivalent.
But if you want to use a lock to prevent it you can do so as follows:
results = HttpContext.Current.Cache[query];
if (results == null)
{
lock(someLock)
{
results = HttpContext.Current.Cache[query];
if (results == null)
{
results = GetResultsFromSomewhere();
HttpContext.Current.Cache.Add(query, results,...);
}
}
}
return results;

Your code is correct. You are also using double-if-sandwitching-lock which will prevent race conditions which is a common pitfall when not used. This will no lock access to existing stuff in the cache.
The only problem is when many clients are inserting into the cache at the same time, and they will queue behind the lock but what I would do is to put the results = GetResultsFromSlowDb(query); outside the lock:
public static string DoSearch(string query)
{
string results = "";
if (HttpContext.Current.Cache[query] == null)
{
results = GetResultsFromSlowDb(query); // HERE
lock (_cacheLock)
{
if (HttpContext.Current.Cache[query] == null)
{
HttpContext.Current.Cache.Add(query, results, null, DateTime.Now.AddHours(1), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
}
else
{
results = HttpContext.Current.Cache[query].ToString();
}
}
}
else
{
results = HttpContext.Current.Cache[query].ToString();
}
If this is slow, your problem is elsewhere.

Related

Using a Try Catch to process logic

The below method iterates through a Try-Catch until a valid object is returned by the statement driver.FindElement(By.Id(elementId)). If the element it is searching for doesn't exist yet (i.e. if the page is still loading for example) then an exception is thrown.
public static IWebElement AwaitElementLoadById(IWebDriver driver, string elementId)
{
bool result = false;
while (!result)
{
try
{
return driver.FindElement(By.Id(elementId));
}
catch (Exception)
{
result = false;
}
}
return null;
}
This works fine and as expected, however I remember reading a while back that Try-Catch statements should not be used to process/drive logic and only used for the desired purpose of handling exceptions.
My question then is, is there a more acceptable way of processing this kind of scenario without using a Try Catch to actually drive the logic.
Caveat: I know that this could potentially cause an infinite loop. In my working example I have a timeout on this method but for the sake of simplicity I have excluded from this snippet.
So the below worked for me in the end:
public static IWebElement AwaitElementLoadByPath(IWebDriver driver, string elementPath, int timoutDuration)
{
bool result = false;
DateTime starttime = DateTime.Now;
while ((!result) && (starttime.AddMilliseconds(timoutDuration) > DateTime.Now))
{
IWebElement returnValue = driver.FindElements(By.XPath(elementPath)).FirstOrDefault();
if (returnValue != null)
{
return returnValue;
}
}
return null;
}

C# Optimized Merge Algorithm

I have a data pull service through which my C# application pulls data. Data is pulled in using multiple jobs and once the data request is complete, the data pull service calls the notify method which I have implemented in my application class.
The following is the notify method code. It just checks if results is non-empty then calls mergeResults in new thread.
public override void notify(List<IFields> results)
{
if (!results.IsNullOrEmpty())
{
Task.Run(() => { mergeResults(results); });
}
}
I am using a List to store final merge results.
List<IFields> mergedResults;
I am using object mergeLock for mutual exclusion.
Here's the merge logic I am using:
public void mergeResults(List<IFieldsByPrePost> results)
{
lock (mergeLock)
{
foreach (var result in results)
{
if (mergedResults.Count > 0)
{
var properties = mergedResults.First().getDiffProperties();
bool isMatch = false;
foreach (var mergedResult in mergedResults)
{
isMatch = true;
foreach (var property in properties)
{
var value1 = mergedResult.GetType().GetProperty(property).GetValue(mergedResult).ToString();
var value2 = result.GetType().GetProperty(property).GetValue(result).ToString();
if (value1 != value2) { isMatch = false; break; }
}
if (isMatch)
{
mergedResult.Count += result.Count;
break;
}
}
if (!isMatch)
{
mergedResults.Add(result);
}
}
else
{
mergedResults.Add(result);
}
}
}
}
The above logic works but it is very slow, whenever a large set of results is passed to the method.
Also, the notify method is called multiple times by the data pull service with different result sets, further slowing it down.
I am looking for a better approach to solve this problem.
TLDR; This algorithm is slow, can anyone show me a way to make it run faster?
I'd suggest, that IFields and/or IFieldsByPrePost derive from
IEquatable<IFields> and/or IEquatable<IFieldsByPrePost>.
So you can just test equality with
IFields fields1;
IFieldsByPrePost fields2;
bool equal = fields1.Equals(fields2);
This way you get around the Reflection, which is slowing your code down.
Then its just
foreach (var result in results)
{
if (!mergedResults.Any(x => x.Equals(result))
{
mergedResults.Add(result);
}
}
I don't know, what you are doing with the
mergedResult.Count,
so I am ommiting this.
The thing that sticks out to me first is that the mergeResults method isn't generic, so I'm not sure why reflection is necessary. Removing the lines:
var value1 = mergedResult.GetType().GetProperty(property).GetValue(mergedResult).ToString();
var value2 = result.GetType().GetProperty(property).GetValue(result).ToString();
if (value1 != value2) { isMatch = false; break; }
and using the direct property:
if(mergedResult.Property1 == result.Property1) { isMatch = false; break; }
could help.

Hangfire get last execution time

I'm using hangfire 1.5.3. In my recurring job I want to call a service that uses the time since the last run. Unfortunately the LastExecution is set to the current time, because the job data was updated before executing the job.
Job
public abstract class RecurringJobBase
{
protected RecurringJobDto GetJob(string jobId)
{
using (var connection = JobStorage.Current.GetConnection())
{
return connection.GetRecurringJobs().FirstOrDefault(p => p.Id == jobId);
}
}
protected DateTime GetLastRun(string jobId)
{
var job = GetJob(jobId);
if (job != null && job.LastExecution.HasValue)
{
return job.LastExecution.Value.ToLocalTime();
}
return DateTime.Today;
}
}
public class NotifyQueryFilterSubscribersJob : RecurringJobBase
{
public const string JobId = "NotifyQueryFilterSubscribersJob";
private readonly IEntityFilterChangeNotificationService _notificationService;
public NotifyQueryFilterSubscribersJob(IEntityFilterChangeNotificationService notificationService)
{
_notificationService = notificationService;
}
public void Run()
{
var lastRun = GetLastRun(JobId);
_notificationService.CheckChangesAndSendNotifications(DateTime.Now - lastRun);
}
}
Register
RecurringJob.AddOrUpdate<NotifyQueryFilterSubscribersJob>(NotifyQueryFilterSubscribersJob.JobId, job => job.Run(), Cron.Minutely, TimeZoneInfo.Local);
I know, that it is configured as minutely, so I could calculate the time roughly. But I'd like to have a configuration independent implementation. So my Question is: How can I implement RecurringJobBase.GetLastRun to return the time of the previous run?
To address my comment above, where you might have more than one type of recurring job running but want to check previous states, you can check that the previous job info actually relates to this type of job by the following (although this feels a bit hacky/convoluted).
If you're passing the PerformContext into the job method than you can use this:
var jobName = performContext.BackgroundJob.Job.ToString();
var currentJobId = int.Parse(performContext.BackgroundJob.Id);
JobData jobFoundInfo = null;
using (var connection = JobStorage.Current.GetConnection()) {
var decrementId = currentJobId;
while (decrementId > currentJobId - 50 && decrementId > 1) { // try up to 50 jobs previously
decrementId--;
var jobInfo = connection.GetJobData(decrementId.ToString());
if (jobInfo.Job.ToString().Equals(jobName)) { // **THIS IS THE CHECK**
jobFoundInfo = jobInfo;
break;
}
}
if (jobFoundInfo == null) {
throw new Exception($"Could not find the previous run for job with name {jobName}");
}
return jobFoundInfo;
}
You could take advantage of the fact you already stated - "Unfortunately the LastExecution is set to the current time, because the job data was updated before executing the job".
The job includes the "LastJobId" property which seems to be an incremented Id. Hence, you should be able to get the "real" previous job by decrement LastJobId and querying the job data for that Id.
var currentJob = connection.GetRecurringJobs().FirstOrDefault(p => p.Id == CheckForExpiredPasswordsId);
if (currentJob == null)
{
return null; // Or whatever suits you
}
var previousJob = connection.GetJobData((Convert.ToInt32(currentJob.LastJobId) - 1).ToString());
return previousJob.CreatedAt;
Note that this is the time of creation, not execution. But it might be accurate enough for you. Bear in mind the edge case when it is your first run, hence there will be no previous job.
After digging around, I came up with the following solution.
var lastSucceded = JobStorage.Current.GetMonitoringApi().SucceededJobs(0, 1000).OrderByDescending(j => j.Value.SucceededAt).FirstOrDefault(j => j.Value.Job.Method.Name == "MethodName" && j.Value.Job.Type.FullName == "NameSpace.To.Class.Containing.The.Method").Value;
var lastExec = lastSucceded.SucceededAt?.AddMilliseconds(Convert.ToDouble(-lastSucceded.TotalDuration));
It's not perfect but i think a little cleaner than the other solutions.
Hopefully they will implement an official way soon.
The answer by #Marius Steinbach is often good enough but if you have thousands of job executions (my case) loading all of them from DB doesn't seem that great. So finally I decided to write a simple SQL query and use it directly (this is for PostgreSQL storage though changing it to SqlServer should be straightforward):
private async Task<DateTime?> GetLastSuccessfulExecutionTime(string jobType)
{
await using var conn = new NpgsqlConnection(_connectionString);
if (conn.State == ConnectionState.Closed)
conn.Open();
await using var cmd = new NpgsqlCommand(#"
SELECT s.data FROM hangfire.job j
LEFT JOIN hangfire.state s ON j.stateid = s.id
WHERE j.invocationdata LIKE $1 AND j.statename = $2
ORDER BY s.createdat DESC
LIMIT 1", conn)
{
Parameters =
{
new() { Value = $"%{jobType}%" } ,
new() { Value = SucceededState.StateName }
}
};
var result = await cmd.ExecuteScalarAsync();
if (result is not string data)
return null;
var stateData = JsonSerializer.Deserialize<Dictionary<string, string>>(data);
return JobHelper.DeserializeNullableDateTime(stateData?.GetValueOrDefault("SucceededAt"));
}
Use this method that return Last exucution time and Next execution time of one job. this method return last and next execution time of one job.
public static (DateTime?, DateTime?) GetExecutionDateTimes(string jobName)
{
DateTime? lastExecutionDateTime = null;
DateTime? nextExecutionDateTime = null;
using (var connection = JobStorage.Current.GetConnection())
{
var job = connection.GetRecurringJobs().FirstOrDefault(p => p.Id == jobName);
if (job != null && job.LastExecution.HasValue)
lastExecutionDateTime = job.LastExecution;
if (job != null && job.NextExecution.HasValue)
nextExecutionDateTime = job.NextExecution;
}
return (lastExecutionDateTime, nextExecutionDateTime);
}

Returning the first method that works, more elegant way?

Recently I've found myself writing methods which call other methods in succession and setting some value based on whichever method returns an appropriate value first. What I've been doing is setting the value with one method, then checking the value and if it's not good then I check the next one. Here's a recent example:
private void InitContent()
{
if (!String.IsNullOrEmpty(Request.QueryString["id"]))
{
Content = GetContent(Convert.ToInt64(Request.QueryString["id"]));
ContentMode = ContentFrom.Query;
}
if (Content == null && DefaultId != null)
{
Content = GetContent(DefaultId);
ContentMode = ContentFrom.Default;
}
if (Content == null) ContentMode = ContentFrom.None;
}
Here the GetContent method should be returning null if the id isn't in the database. This is a short example, but you can imagine how this might get clunky if there were more options. Is there a better way to do this?
The null coalescing operator might have the semantics you want.
q = W() ?? X() ?? Y() ?? Z();
That's essentially the same as:
if ((temp = W()) == null && (temp = X()) == null && (temp == Y()) == null)
temp = Z();
q = temp;
That is, q is the first non-null of W(), X(), Y(), or if all of them are null, then Z().
You can chain as many as you like.
The exact semantics are not quite like I sketched out; the type conversion rules are tricky. See the spec if you need the exact details.
You could also do something a little more sneaky, along the lines of this:
private Int64? GetContentIdOrNull(string id)
{
return string.IsNullOrEmpty(id) ? null : (Int64?)Convert.ToInt64(id);
}
private Int64? GetContentIdOrNull(DefaultIdType id)
{
return id;
}
private void InitContent()
{
// Attempt to get content from multiple sources in order of preference
var contentSources = new Dictionary<ContentFrom, Func<Int64?>> {
{ ContentFrom.Query, () => GetContentIdOrNull(Request.QueryString["id"]) },
{ ContentFrom.Default, () => GetContentIdOrNull(DefaultId) }
};
foreach (var source in contentSources) {
var id = source.Value();
if (!id.HasValue) {
continue;
}
Content = GetContent(id.Value);
ContentMode = source.Key;
if (Content != null) {
return;
}
}
// Default
ContentMode = ContentFrom.None;
}
That would help if you had many more sources, at the cost of increased complexity.
Personally, I find when I have lots of statements that are seemingly disparate, it's time to make some functions.
private ContentMode GetContentMode(){
}
private Content GetContent(int id){
}
private Content GetContent(HttpRequest request){
return GetContent(Convert.ToInt64(request.QueryString["id"]));
}
private void InitContent(){
ContentMode mode = GetContentMode();
Content = null;
switch(mode){
case ContentMode.Query:
GetContent(Request);
break;
case ContentMode.Default:
GetContent(DefaultId);
break;
case ContentMode.None:
... handle none case...
break;
}
}
This way, you separate your intentions - first step, determine the content mode. Then, get the content.
I suggest you try some kind of Factory design pattern for this case. You can abstract the content create procedure by register different creators. Moreover, you can add preference on each creator for your own logic. Besides, I suggest you encapsulate all data related to Content just like "ContentDefinition" class from other's post.
In general, you need to know that there is always a trade off between flexibility and efficiency. Sometime your first solution is good enough:)
Ok, because I noticed a bit late that you actually wanted the ContentFrom mode as well, I've done my best to come up with a translation of your sample below my original answer
In general I use the following paradigm for cases like this. Search and replace your specific methods here and there :)
IEnumerable<T> ValueSources()
{
yield return _value?? _alternative;
yield return SimpleCalculationFromCache();
yield return ComplexCalculation();
yield return PromptUIInputFallback("Please help by entering a value for X:");
}
T EffectiveValue { get { return ValueSources().FirstOrDefault(v => v!=null); } }
Note how you can now make v!=null arbitrarily 'interesting' for your purposes.
Note also how lazy evaluation makes sure that the calculations are never done when _value or _alternative are set to 'interesting' values
Here is my initial attempt at putting your sample into this mold. Note how I added quite a lot of plumbing to make sure this actually compiles into standalone C# exe:
using System.Collections.Generic;
using System.Linq;
using System;
using T=System.String;
namespace X { public class Y
{
public static void Main(string[]args)
{
var content = Sources().FirstOrDefault(c => c); // trick: uses operator bool()
}
internal protected struct Content
{
public T Value;
public ContentFrom Mode;
//
public static implicit operator bool(Content specimen) { return specimen.Mode!=ContentFrom.None && null!=specimen.Value; }
}
private static IEnumerable<Content> Sources()
{
// mock
var Request = new { QueryString = new [] {"id"}.ToDictionary(a => a) };
if (!String.IsNullOrEmpty(Request.QueryString["id"]))
yield return new Content { Value = GetContent(Convert.ToInt64(Request.QueryString["id"])), Mode = ContentFrom.Query };
if (DefaultId != null)
yield return new Content { Value = GetContent((long) DefaultId), Mode = ContentFrom.Default };
yield return new Content();
}
public enum ContentFrom { None, Query, Default };
internal static T GetContent(long id) { return "dummy"; }
internal static readonly long? DefaultId = 42;
} }
private void InitContent()
{
Int64? id = !String.IsNullOrEmpty(Request.QueryString["id"])
? Convert.ToInt64(Request.QueryString["id"])
: null;
if (id != null && (Content = GetContent(id)) != null)
ContentMode = ContentFrom.Query;
else if(DefaultId != null && (Content = GetContent(DefaultId)) != null)
ContentMode = ContentFrom.Default;
else
ContentMode = ContentFrom.None;
}

What is the best way to lock cache in asp.net?

I know in certain circumstances, such as long running processes, it is important to lock ASP.NET cache in order to avoid subsequent requests by another user for that resource from executing the long process again instead of hitting the cache.
What is the best way in c# to implement cache locking in ASP.NET?
Here's the basic pattern:
Check the cache for the value, return if its available
If the value is not in the cache, then implement a lock
Inside the lock, check the cache again, you might have been blocked
Perform the value look up and cache it
Release the lock
In code, it looks like this:
private static object ThisLock = new object();
public string GetFoo()
{
// try to pull from cache here
lock (ThisLock)
{
// cache was empty before we got the lock, check again inside the lock
// cache is still empty, so retreive the value here
// store the value in the cache here
}
// return the cached value here
}
For completeness a full example would look something like this.
private static object ThisLock = new object();
...
object dataObject = Cache["globalData"];
if( dataObject == null )
{
lock( ThisLock )
{
dataObject = Cache["globalData"];
if( dataObject == null )
{
//Get Data from db
dataObject = GlobalObj.GetData();
Cache["globalData"] = dataObject;
}
}
}
return dataObject;
There is no need to lock the whole cache instance, rather we only need to lock the specific key that you are inserting for.
I.e. No need to block access to the female toilet while you use the male toilet :)
The implementation below allows for locking of specific cache-keys using a concurrent dictionary. This way you can run GetOrAdd() for two different keys at the same time - but not for the same key at the same time.
using System;
using System.Collections.Concurrent;
using System.Web.Caching;
public static class CacheExtensions
{
private static ConcurrentDictionary<string, object> keyLocks = new ConcurrentDictionary<string, object>();
/// <summary>
/// Get or Add the item to the cache using the given key. Lazily executes the value factory only if/when needed
/// </summary>
public static T GetOrAdd<T>(this Cache cache, string key, int durationInSeconds, Func<T> factory)
where T : class
{
// Try and get value from the cache
var value = cache.Get(key);
if (value == null)
{
// If not yet cached, lock the key value and add to cache
lock (keyLocks.GetOrAdd(key, new object()))
{
// Try and get from cache again in case it has been added in the meantime
value = cache.Get(key);
if (value == null && (value = factory()) != null)
{
// TODO: Some of these parameters could be added to method signature later if required
cache.Insert(
key: key,
value: value,
dependencies: null,
absoluteExpiration: DateTime.Now.AddSeconds(durationInSeconds),
slidingExpiration: Cache.NoSlidingExpiration,
priority: CacheItemPriority.Default,
onRemoveCallback: null);
}
// Remove temporary key lock
keyLocks.TryRemove(key, out object locker);
}
}
return value as T;
}
}
Just to echo what Pavel said, I believe this is the most thread safe way of writing it
private T GetOrAddToCache<T>(string cacheKey, GenericObjectParamsDelegate<T> creator, params object[] creatorArgs) where T : class, new()
{
T returnValue = HttpContext.Current.Cache[cacheKey] as T;
if (returnValue == null)
{
lock (this)
{
returnValue = HttpContext.Current.Cache[cacheKey] as T;
if (returnValue == null)
{
returnValue = creator(creatorArgs);
if (returnValue == null)
{
throw new Exception("Attempt to cache a null reference");
}
HttpContext.Current.Cache.Add(
cacheKey,
returnValue,
null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
System.Web.Caching.Cache.NoSlidingExpiration,
CacheItemPriority.Normal,
null);
}
}
}
return returnValue;
}
Craig Shoemaker has made an excellent show on asp.net caching:
http://polymorphicpodcast.com/shows/webperformance/
I have come up with the following extension method:
private static readonly object _lock = new object();
public static TResult GetOrAdd<TResult>(this Cache cache, string key, Func<TResult> action, int duration = 300) {
TResult result;
var data = cache[key]; // Can't cast using as operator as TResult may be an int or bool
if (data == null) {
lock (_lock) {
data = cache[key];
if (data == null) {
result = action();
if (result == null)
return result;
if (duration > 0)
cache.Insert(key, result, null, DateTime.UtcNow.AddSeconds(duration), TimeSpan.Zero);
} else
result = (TResult)data;
}
} else
result = (TResult)data;
return result;
}
I have used both #John Owen and #user378380 answers. My solution allows you to store int and bool values within the cache aswell.
Please correct me if there's any errors or whether it can be written a little better.
I saw one pattern recently called Correct State Bag Access Pattern, which seemed to touch on this.
I modified it a bit to be thread-safe.
http://weblogs.asp.net/craigshoemaker/archive/2008/08/28/asp-net-caching-and-performance.aspx
private static object _listLock = new object();
public List List() {
string cacheKey = "customers";
List myList = Cache[cacheKey] as List;
if(myList == null) {
lock (_listLock) {
myList = Cache[cacheKey] as List;
if (myList == null) {
myList = DAL.ListCustomers();
Cache.Insert(cacheKey, mList, null, SiteConfig.CacheDuration, TimeSpan.Zero);
}
}
}
return myList;
}
This article from CodeGuru explains various cache locking scenarios as well as some best practices for ASP.NET cache locking:
Synchronizing Cache Access in ASP.NET
I've wrote a library that solves that particular issue: Rocks.Caching
Also I've blogged about this problem in details and explained why it's important here.
I modified #user378380's code for more flexibility. Instead of returning TResult now returns object for accepting different types in order. Also adding some parameters for flexibility. All the idea belongs to
#user378380.
private static readonly object _lock = new object();
//If getOnly is true, only get existing cache value, not updating it. If cache value is null then set it first as running action method. So could return old value or action result value.
//If getOnly is false, update the old value with action result. If cache value is null then set it first as running action method. So always return action result value.
//With oldValueReturned boolean we can cast returning object(if it is not null) appropriate type on main code.
public static object GetOrAdd<TResult>(this Cache cache, string key, Func<TResult> action,
DateTime absoluteExpireTime, TimeSpan slidingExpireTime, bool getOnly, out bool oldValueReturned)
{
object result;
var data = cache[key];
if (data == null)
{
lock (_lock)
{
data = cache[key];
if (data == null)
{
oldValueReturned = false;
result = action();
if (result == null)
{
return result;
}
cache.Insert(key, result, null, absoluteExpireTime, slidingExpireTime);
}
else
{
if (getOnly)
{
oldValueReturned = true;
result = data;
}
else
{
oldValueReturned = false;
result = action();
if (result == null)
{
return result;
}
cache.Insert(key, result, null, absoluteExpireTime, slidingExpireTime);
}
}
}
}
else
{
if(getOnly)
{
oldValueReturned = true;
result = data;
}
else
{
oldValueReturned = false;
result = action();
if (result == null)
{
return result;
}
cache.Insert(key, result, null, absoluteExpireTime, slidingExpireTime);
}
}
return result;
}
The accepted answer (recommending reading outside of the lock) is very bad advice and is being implemented since 2008. It could work if the cache uses a concurrent dictionary, but that itself has a lock for reads.
Reading outside of the lock means that other threads could be modifying the cache in the middle of read. This means that the read could be inconsistent.
For example, depending on the implementation of the cache (probably a dictionary whose internals are unknown), the item could be checked and found in the cache, at a certain index in the underlying array of the cache, then another thread could modify the cache so that the items from the underlying array are no longer in the same order, and then the actual read from the cache could be from a different index / address.
Another scenario is that the read could be from an index that is now outside of the underlying array (because items were removed), so you can get exceptions.

Categories

Resources