如果我想强制从锁中访问一个对象,如下所示:
var obj = new MyObject();
lock (obj)
{
obj.Date = DateTime.Now;
obj.Name = "My Name";
}
是否有可能从
AddOne
和 RemoveOne
函数中检测当前执行上下文是否在锁内?就像是:
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);
}
这很简单,但它有两个问题:
mylock 对象,有点
滥用 IDisposable
界面。
SetDate
和 SetName
函数更改为属性,为清楚起见。
最佳答案
没有在运行时检查这种情况的记录方法,如果有,我会对使用它的任何代码表示怀疑,因为任何基于调用堆栈改变其行为的代码都很难调试。
真正的 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
,它验证当前线程的锁是否存在(因为记住, locks
是 ThreadStatic
),如果是,则将其删除条件满足,否则抛出异常。 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) 允许任何线程拥有对象,只要没有其他东西在使用它。