Reputation: 6540
We have a legacy ASP.NET 2.0 environment where each page execution is authenticated to a specific user, and therefore I have an integer representing the logged-in user's ID.
On one of the pages I need to run some code where I want to prevent the user from performing a duplicate action. Finding it difficult to guarantee this can't happen, even though we're doing basic dupe-prevention checking.
Obviously I could create a static object and do a lock(myObject) { ... }
around the entire piece of code to try and help prevent some of these race conditions. But I don't want to create a bottleneck for everyone ... just want to stop the same logged-in user from running the code simultaneously or nearly simultaneously.
So I am thinking of creating an object instance for each user, and storing it in a cache based on their user id. Then I lookup that object, and if the object is found, I lock on it. If not found, I first create/cache it, then lock on it.
Does this make sense? Is there a better way to accomplish what I'm after?
Something like this is what I'm thinking:
public class MyClass
{
private static object lockObject = new object(); // global locking object
public void Page_Load()
{
string cachekey = "preventdupes:" + UserId.ToString();
object userSpecificLock = null;
// This part would synchronize among all requests, but should be quick
// as it is simply trying to find out if a user-specific lock object
// exists, and if so, it gets it. Otherwise, it creates and stores it.
lock (lockObject)
{
userSpecificLock = HttpRuntime.Cache.Get(cachekey);
if (userSpecificLock == null)
{
userSpecificLock = new object();
// Cache the locking object on a sliding 30 minute window
HttpRuntime.Cache.Add(cachekey, userSpecificLock, null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
new TimeSpan(0, 30, 0),
System.Web.Caching.CacheItemPriority.AboveNormal, null);
}
}
// Now we have obtained an instance of an object specific to the user,
// and we'll lock the next block of code specifically to them.
lock (userSpecificLock)
{
try
{
// Perform some operations to check our database to see if the
// transaction already occurred for this user, and if not,
// perform the transaction, and then record it into our db.
}
catch (Exception)
{
// Rollback anything our code has done up until this exception,
// so that if the user tries again, it will work.
}
}
}
}
Upvotes: 1
Views: 1057
Reputation: 66649
The solution is to use mutex.
Mutex can be named, so you can name your user id, and they are work for the full computer, so they are work if you have many processes under the same pool (web garden).
More to read:
http://en.wikipedia.org/wiki/Mutual_exclusion
Asp.Net. Synchronization access(mutex)
http://www.dotnetperls.com/mutex
MSDN Mutex with example
The lock
The lock is work only inside the same and parent threads and you can use them only for synchronized static variables. But also the HttpRuntime.Cache
is a static memory, that is means that if you have many processes under the same pool (web garden), you have many different Cache variables.
The page is also automatically synchronized by the session. So if you have disable the session for this page, then the mutex have a point, if not, the session all ready locks the page_load (with mutex), and the mutex that you will going to place have no meaning.
Some reference about:
ASP.NET Server does not process pages asynchronously
Is Session variable thread-safe within a Parallel.For loop in ASP.Net page
HttpContext.Current is null when in method called from PageAsyncTask
Upvotes: 2