Reputation: 48402
I have the following code:
var sequence = from row in CicApplication.DistributorBackpressure44Cache
where row.Coater == this.Coater && row.IsDistributorInUse
select new GenericValue
{
ReadTime = row.CoaterTime.Value,
Value = row.BackpressureLeft
};
this.EvaluateBackpressure(sequence, "BackpressureLeftTarget");
And DistributorBackpressure44Cache is defined as follows:
internal static List<DistributorBackpressure44> DistributorBackpressure44Cache
{
get
{
return _distributorBackpressure44;
}
}
This is part of a heavily threaded application where DistributorBackpressure44Cache could be being refreshed in one thread, and queried from, as shown above, in another thread. The variable 'sequence' above is an IEnumerable, which is passed to the method shown, and then potentially passed to the other methods, before actually being executed. My concern is this. What will happen with the above query if the DistributorBackpressure44Cache is being refreshed (cleared and repopulated) when the query is actually executed?
It wouldn't do any good to put a lock around this code because this query actually gets executed at some point later (unless I were to convert it to a list immediately).
Upvotes: 1
Views: 81
Reputation: 64068
If your design can tolerate it, you could ensure snapshot level isolation with this code and avoid locking altogether. However, you would need to do the following:
Make DistributorBackpressure44Cache
return a ReadOnlyCollection<T>
instead, this way it is explicit you shouldn't mutate this data.
Ensure that any mutations to _distributorBackpressure44
occur on a copy and result in an atomic assignment back to _distributorBackpressure44
when complete.
var cache = _distributorBackpressure44.ToList();
this.RefreshCache(cache); // this assumes you *need* to know
// about the structure of the old list
// a design where this is not required
// is preferred
_distributorBackpressure44 = cache; // some readers will have "old"
// views of the cache, but all readers
// from some time T (where T < Twrite)
// will use the same "snapshot"
Upvotes: 1
Reputation: 14786
You can convert it to a list immediately (might be best--)
or
You can put a lock in the get for DistributorBackpressure44 that synchs with the cache refresh lock. You might want to include a locked and unlocked accessor; use the unlocked accessor when the result is going to be used immediately, and the locked one when the accessor is going to be used in a deferred execution situation.
Note that even that won't work if the cache refresh mutates the list _distributorBackpress44, only if it just replaces the referenced list.
Upvotes: 1
Reputation: 1165
Without knowing more about your architecture options, you could do something like this.
lock(CicApplication.DistributorBackpressure44Cache)
{
var sequence = from row in CicApplication.DistributorBackpressure44Cache
where row.Coater == this.Coater && row.IsDistributorInUse
select new GenericValue
{
ReadTime = row.CoaterTime.Value,
Value = row.BackpressureLeft
};
}
this.EvaluateBackpressure(sequence, "BackpressureLeftTarget");
Then in the code where you do the clear/update you would have something like this.
lock(CicApplication.DistributorBackpressure44Cache)
{
var objCache = CicApplication.DistributorBackpressure44Cache
objCache.Clear();
// code to add back items here
// [...]
}
It would be cleaner to have a central class (Singleton pattern maybe?) that controls everything surrounding the cache. But I don't know how feasible this is (i.e. putting the query code into another class and passing the parameters in). In lieu of something fancier, the above solution should work as long as you consistently remember to lock() each and every time you ever read/write to this object.
Upvotes: 0