C #: Can you determine if the current execution context is in `lock (this)`?

If I have an object that I would like to get access from inside the lock, for example:

var obj = new MyObject(); lock (obj) { obj.Date = DateTime.Now; obj.Name = "My Name"; } 

Is it possible to determine from the AddOne and RemoveOne functions whether the current execution context is within a lock?

Sort of:

 Monitor.AreWeCurrentlyEnteredInto(this) 

Edit: (to clarify intent)

The goal is to be able to reject any changes made outside the lock so that all changes to the object itself are transactional and thread safe. Locking on a mutex inside the object itself does not provide a transactional character for editing.


I know this can be done:

 var obj = new MyObject(); obj.MonitorEnterThis(); try { obj.Date = DateTime.Now; obj.Name = "My Name"; } finally { obj.MonitorExitThis(); } 

But this will allow any other thread to call the Add / Remove functions without first calling Enter , thereby bypassing the protection.


Edit 2:

Here is what I am doing now:

 var obj = new MyObject(); using (var mylock = obj.Lock()) { obj.SetDate(DateTime.Now, mylock); obj.SetName("New Name", mylock); } 

It's simple enough, but it has two problems:

  • I am implementing IDisposable on the mylock object, which is a bit of abuse on the IDisposable interface.

  • I would like to change the SetDate and SetName to Properties, for clarity.

+4
source share
7 answers

There is no documented method for checking this state at runtime, and if that were the case, I would be suspicious of any code used by it, because any code that changes its behavior based on the call stack will be very difficult to debug.

The true semantics of ACID are not trivial to implement, and I personally will not try; that we have databases, and you can use the database in memory if you need fast or portable code. If you just need forced single-threaded semantics, this is a little easier than taming, although as a disclaimer, I should mention that in the end you would be better off just providing atomic operations, rather than trying to prevent multithreading, threading access.

Suppose you have a very good reason for this. Here is a concept proof class that you could use:

 public interface ILock : IDisposable { } public class ThreadGuard { private static readonly object SlotMarker = new Object(); [ThreadStatic] private static Dictionary<Guid, object> locks; private Guid lockID; private object sync = new Object(); public void BeginGuardedOperation() { lock (sync) { if (lockID == Guid.Empty) throw new InvalidOperationException("Guarded operation " + "was blocked because no lock has been obtained."); object currentLock; Locks.TryGetValue(lockID, out currentLock); if (currentLock != SlotMarker) { throw new InvalidOperationException("Guarded operation " + "was blocked because the lock was obtained on a " + "different thread from the calling thread."); } } } public ILock GetLock() { lock (sync) { if (lockID != Guid.Empty) throw new InvalidOperationException("This instance is " + "already locked."); lockID = Guid.NewGuid(); Locks.Add(lockID, SlotMarker); return new ThreadGuardLock(this); } } private void ReleaseLock() { lock (sync) { if (lockID == Guid.Empty) throw new InvalidOperationException("This instance cannot " + "be unlocked because no lock currently exists."); object currentLock; Locks.TryGetValue(lockID, out currentLock); if (currentLock == SlotMarker) { Locks.Remove(lockID); lockID = Guid.Empty; } else throw new InvalidOperationException("Unlock must be invoked " + "from same thread that invoked Lock."); } } public bool IsLocked { get { lock (sync) { return (lockID != Guid.Empty); } } } protected static Dictionary<Guid, object> Locks { get { if (locks == null) locks = new Dictionary<Guid, object>(); return locks; } } #region Lock Implementation class ThreadGuardLock : ILock { private ThreadGuard guard; public ThreadGuardLock(ThreadGuard guard) { this.guard = guard; } public void Dispose() { guard.ReleaseLock(); } } #endregion } 

Much happens here, but I will break it for you:

  • Current locks (for a thread) are stored in the [ThreadStatic] field, which provides a type-safe, local thread storage. The field is shared between ThreadGuard instances, but each instance uses its own key (Guid).

  • Two basic operations: GetLock , which checks that the lock has not already been completed, and then adds its own lock and ReleaseLock , which checks whether the lock exists for the current thread (because remember locks is ThreadStatic ) and removes it if this condition done, otherwise throws an exception.

  • The last BeginGuardedOperation operation BeginGuardedOperation intended for use by classes that have ThreadGuard instances. This is basically a statement of the kind, it checks that the current executable thread belongs to some lock assigned to this ThreadGuard , and ThreadGuard if the condition is not met.

  • There is also an ILock interface (which does nothing but the IDisposable output), and a one-time internal ThreadGuardLock for its implementation, which contains a link to the ThreadGuard created ThreadGuard it and calls its ReleaseLock method when it is placed. Note that ReleaseLock is confidential, so ThreadGuardLock.Dispose is the only public access to the release function, which is good - we only need one entry point to receive and release.

To use ThreadGuard , you must include it in another class:

 public class MyGuardedClass { private int id; private string name; private ThreadGuard guard = new ThreadGuard(); public MyGuardedClass() { } public ILock Lock() { return guard.GetLock(); } public override string ToString() { return string.Format("[ID: {0}, Name: {1}]", id, name); } public int ID { get { return id; } set { guard.BeginGuardedOperation(); id = value; } } public string Name { get { return name; } set { guard.BeginGuardedOperation(); name = value; } } } 

All this makes use of the BeginGuardedOperation method as a statement, as described above. Please note that I am not trying to protect read and write conflicts, only multiple-letter conflicts. If you need reader synchronization, then you will either need to require the same lock for reading (maybe not so good), use the additional lock in MyGuardedClass (the most direct solution) or change ThreadGuard to set and acquire a true β€œlock” from using the Monitor class (be careful).

And here is a test program for playing with:

 class Program { static void Main(string[] args) { MyGuardedClass c = new MyGuardedClass(); RunTest(c, TestNoLock); RunTest(c, TestWithLock); RunTest(c, TestWithDisposedLock); RunTest(c, TestWithCrossThreading); Console.ReadLine(); } static void RunTest(MyGuardedClass c, Action<MyGuardedClass> testAction) { try { testAction(c); Console.WriteLine("SUCCESS: Result = {0}", c); } catch (Exception ex) { Console.WriteLine("FAIL: {0}", ex.Message); } } static void TestNoLock(MyGuardedClass c) { c.ID = 1; c.Name = "Test1"; } static void TestWithLock(MyGuardedClass c) { using (c.Lock()) { c.ID = 2; c.Name = "Test2"; } } static void TestWithDisposedLock(MyGuardedClass c) { using (c.Lock()) { c.ID = 3; } c.Name = "Test3"; } static void TestWithCrossThreading(MyGuardedClass c) { using (c.Lock()) { c.ID = 4; c.Name = "Test4"; ThreadPool.QueueUserWorkItem(s => RunTest(c, cc => cc.ID = 5)); Thread.Sleep(2000); } } } 

As the code suggests (hopefully), only the TestWithLock method TestWithLock completely. The TestWithCrossThreading method partially succeeds - the worker thread crashes, but the main thread has no problems (which again is the desired behavior here).

This is not intended for production-ready code, but it should give you a basic idea of ​​what needs to be done in order to (a) prevent end-to-end calls and (b) allow any thread to own the object if nothing else uses it.

+2
source

I do not think that this is possible without tracking the state (for example, using a semaphore). But even if that were the case, it would be a gross violation of encapsulation. Typically, your methods do not care if they execute in a specific locking context.

+4
source

Allows you to re-map your class so that it really works as a transaction.

 using (var transaction = account.BeginTransaction()) { transaction.Name = "blah"; transaction.Date = DateTime.Now; transaction.Comit(); } 

Changes will not be propagated until commit is called. In commit, you can lock and set properties on the target.

+2
source

You can override AddOne and RemoveOne to take the boolean flag, which is set to true if it is called from a lock. I do not see another way.

You can also play with the ExecutionContext class if you want to learn something about the current execution context. You can get the current context by calling ExecutionContext.Capture() .

+1
source

Using the local thread store, you can save block input and exit.

+1
source

If your requirement is that a lock be obtained during any AddOne () or RemoveOne () method, then why not just get a lock inside each method? This should not be a problem if the caller has already acquired a lock for you.

However, if your requirement is that a lock be obtained before calling AddOne () and RemoveOne () together (since other concurrent operations performed on the instance are potentially dangerous), you might want to consider changing the open interface so that the lock can be processed internally without a customer code with details.

One possible way to achieve a later one is to provide methods for Begin and End-Changes that must be called before and after AddOne and RemoveOne. An exception must be thrown if AddOne or RemoveOne is called outside the Start area.

+1
source

I ran into the same problem and created a helper class that looks like this:

 public class BusyLock : IDisposable { private readonly Object _lockObject = new Object(); private int _lockCount; public bool IsBusy { get { return _lockCount > 0; } } public IDisposable Enter() { if (!Monitor.TryEnter(_lockObject, TimeSpan.FromSeconds(1.0))) throw new InvalidOperationException("Cannot begin operation as system is already busy"); Interlocked.Increment(ref _lockCount); return this; } public bool TryEnter(out IDisposable busyLock) { if (Monitor.TryEnter(_lockObject)) { busyLock = this; Interlocked.Increment(ref _lockCount); return true; } busyLock = null; return false; } #region IDisposable Members public void Dispose() { if (_lockCount > 0) { Monitor.Exit(_lockObject); Interlocked.Decrement(ref _lockCount); } } #endregion } 

Then you can create an instance wrapped like this:

 public sealed class AutomationManager { private readonly BusyLock _automationLock = new BusyLock(); public IDisposable AutomationLock { get { return _automationLock.Enter(); } } public bool IsBusy { get { return _automationLock.IsBusy; } } } 

And use it as follows:

  public void DoSomething() { using (AutomationLock) { //Do important busy stuff here } } 

In my specific case, I only need a forced lock (two threads should never try to get a lock at the same time if they behave well), so I am making an exception. You can easily change it to perform a more typical lock and still take advantage of IsBusy.

0
source

Source: https://habr.com/ru/post/1306074/


All Articles