Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>This is a tricky problem - you need a distributed lock, or some sort of shared state.</p> <p>Since you already have the database, you could change your implementation from a "static C# lock" and instead the database to manage your lock for you over the whole "transaction".</p> <p>You don't say what database you are using, but if it's SQL Server, then you can use an application lock to achieve this. This lets you explicitly "lock" an object, and all other clients will wait until that object is unlocked. Check out: </p> <p><a href="http://technet.microsoft.com/en-us/library/ms189823.aspx" rel="noreferrer">http://technet.microsoft.com/en-us/library/ms189823.aspx</a></p> <p>I've coded up an example implementation below. Start two instances to test it out.</p> <pre><code>using System; using System.Data; using System.Data.SqlClient; using System.Transactions; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { var locker = new SqlApplicationLock("MyAceApplication", "Server=xxx;Database=scratch;User Id=xx;Password=xxx;"); Console.WriteLine("Aquiring the lock"); using (locker.TakeLock(TimeSpan.FromMinutes(2))) { Console.WriteLine("Lock Aquired, doing work which no one else can do. Press any key to release the lock."); Console.ReadKey(); } Console.WriteLine("Lock Released"); } class SqlApplicationLock : IDisposable { private readonly String _uniqueId; private readonly SqlConnection _sqlConnection; private Boolean _isLockTaken = false; public SqlApplicationLock( String uniqueId, String connectionString) { _uniqueId = uniqueId; _sqlConnection = new SqlConnection(connectionString); _sqlConnection.Open(); } public IDisposable TakeLock(TimeSpan takeLockTimeout) { using (TransactionScope transactionScope = new TransactionScope(TransactionScopeOption.Suppress)) { SqlCommand sqlCommand = new SqlCommand("sp_getapplock", _sqlConnection); sqlCommand.CommandType = CommandType.StoredProcedure; sqlCommand.CommandTimeout = (int)takeLockTimeout.TotalSeconds; sqlCommand.Parameters.AddWithValue("Resource", _uniqueId); sqlCommand.Parameters.AddWithValue("LockOwner", "Session"); sqlCommand.Parameters.AddWithValue("LockMode", "Exclusive"); sqlCommand.Parameters.AddWithValue("LockTimeout", (Int32)takeLockTimeout.TotalMilliseconds); SqlParameter returnValue = sqlCommand.Parameters.Add("ReturnValue", SqlDbType.Int); returnValue.Direction = ParameterDirection.ReturnValue; sqlCommand.ExecuteNonQuery(); if ((int)returnValue.Value &lt; 0) { throw new Exception(String.Format("sp_getapplock failed with errorCode '{0}'", returnValue.Value)); } _isLockTaken = true; transactionScope.Complete(); } return this; } public void ReleaseLock() { using (TransactionScope transactionScope = new TransactionScope(TransactionScopeOption.Suppress)) { SqlCommand sqlCommand = new SqlCommand("sp_releaseapplock", _sqlConnection); sqlCommand.CommandType = CommandType.StoredProcedure; sqlCommand.Parameters.AddWithValue("Resource", _uniqueId); sqlCommand.Parameters.AddWithValue("LockOwner", "Session"); sqlCommand.ExecuteNonQuery(); _isLockTaken = false; transactionScope.Complete(); } } public void Dispose() { if (_isLockTaken) { ReleaseLock(); } _sqlConnection.Close(); } } } } </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload