如果我想强制从锁中访问一个对象,如下所示:

var obj = new MyObject();

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

是否有可能从 AddOneRemoveOne 函数中检测当前执行上下文是否在锁内?

就像是:
Monitor.AreWeCurrentlyEnteredInto(this)

编辑:(用于澄清意图)

这里的目的是能够拒绝在锁之外进行的任何修改,以便对对象本身的所有更改都是事务性的和线程安全的。锁定对象本身内的互斥锁并不能确保编辑具有事务性。

我知道可以这样做:
var obj = new MyObject();

obj.MonitorEnterThis();

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

但这将允许任何其他线程在不首先调用 Enter 的情况下调用 Add/Remove 函数,从而绕过保护。

编辑 2:

这是我目前正在做的事情:
var obj = new MyObject();

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

这很简单,但它有两个问题:
  • 我正在实现 IDisposable
    mylock 对象,有点
    滥用 IDisposable
    界面。
  • 我想将 SetDateSetName 函数更改为
    属性,为清楚起见。
  • 最佳答案

    没有在运行时检查这种情况的记录方法,如果有,我会对使用它的任何代码表示怀疑,因为任何基于调用堆栈改变其行为的代码都很难调试。

    真正的 ACID 语义实现起来并不容易,我个人不会尝试;这就是我们拥有数据库的目的,如果您需要代码快速/可移植,您可以使用内存数据库。如果你只想要强制单线程语义,那是一个更容易驯服的野兽,尽管作为免责声明我应该提到,从长远来看,你最好只提供原子操作而不是试图阻止多线程线程访问。

    假设您有充分的理由想要这样做。这是您可以使用的概念验证类:

    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
    }
    

    这里发生了很多事情,但我会为你分解:
  • 当前锁(每个线程)保存在 [ThreadStatic] 字段中,该字段提供类型安全的线程本地存储。该字段在 ThreadGuard 的实例之间共享,但每个实例使用自己的 key (Guid)。
  • 两个主要操作是 GetLock ,它验证没有锁已经被占用,然后添加自己的锁,以及 ReleaseLock ,它验证当前线程的锁是否存在(因为记住, locksThreadStatic ),如果是,则将其删除条件满足,否则抛出异常。
  • 最后一个操作 BeginGuardedOperation 旨在供拥有 ThreadGuard 实例的类使用。它基本上是一种断言,它验证当前执行的线程是否拥有分配给此 ThreadGuard 的任何锁,并在不满足条件时抛出。
  • 还有一个 ILock 接口(interface)(除了派生自 IDisposable 之外不做任何事情),以及一个一次性内部 ThreadGuardLock 来实现它,它持有对创建它的 ThreadGuard 的引用,并在处理时调用其 ReleaseLock 方法。请注意,ReleaseLock 是私有(private)的,因此 ThreadGuardLock.Dispose 是释放函数的唯一公共(public)访问权限,这很好 - 我们只需要一个用于获取和释放的入口点。

  • 要使用 ThreadGuard ,您需要将它包含在另一个类中:
    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;
            }
        }
    }
    

    如前所述,所有这些都是使用 BeginGuardedOperation 方法作为断言。请注意,我不是要保护读写冲突,而是要保护多写冲突。如果您想要读写器同步,那么您需要使用相同的锁进行读取(可能不太好),在 MyGuardedClass 中使用额外的锁(最直接的解决方案)或更改 ThreadGuard 以公开并获取真正的“使用 Monitor 类锁定”(小心)。

    这是一个可以玩的测试程序:
    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);
            }
        }
    }
    

    正如代码(希望如此)暗示的那样,只有 TestWithLock 方法完全成功。 TestWithCrossThreading 方法部分成功 - 工作线程失败,但主线程没有问题(这又是此处所需的行为)。

    这不是生产就绪代码,但它应该让您了解为了 (a) 防止跨线程调用和 (b) 允许任何线程拥有对象,只要没有其他东西在使用它。

    10-06 08:41