我有一些代码在给定token
的数据库中创建一个新行,或者返回已存在的现有行:
public static RunSet ProvisionRun(ApplicationDbContext context, IIdentity identity, string token, CrewType crewType)
{
var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context));
var user = manager.FindById(identity.GetUserId());
var runSet = user.RunSets.SingleOrDefault(r => r.RunName == token);
if (runSet != null) // Run set already created, they must be uploading new files to this run; return existing run
return runSet;
// Create the run
var newRun = new RunSet
{
Created = DateTime.Now,
CrewType = crewType.ToString(),
RunName = token,
InputFiles = new List<RunFile>()
};
user.RunSets.Add(newRun);
context.SaveChanges();
return newRun;
}
这很好用,但是多个线程可能会立即调用此代码,导致多个行具有相同的RunName
(这会创建一个唯一的约束违规)。为避免这种情况,我可以在此代码周围添加lock
语句:
private static readonly Object _lock = new Object();
public static RunSet ProvisionRun(ApplicationDbContext context, IIdentity identity, string token, CrewType crewType)
{
lock (_lock)
{
// Same stuff here
return newRun;
}
}
这可以解决这个问题。但是,没有两个用户会传入相同的token
,所以我真正希望阻止相同的用户调用该方法,但允许两个不同< / em>用户同时调用该方法。我应该能够锁定用户的身份:
lock (identity.Name) // This is a string
{
// Same stuff here
return newRun;
}
然而,这不起作用。我已经在调试器中逐步完成了它,实际上它只是走过lock
,尽管identity.Name
在两个线程中都是相同的:
我有点倾向于认为lock
关键字适用于精确的引用相等,这是有意义的。但是,在MSDN上挖掘lock documentation时,它明确地说:
我假设框架有一个锁的哈希并将检查对象是否相等。它也可能是文档具有误导性,因为它使用了一个字符串常量来实现。
无论哪种方式,很明显我对锁的理解存在一些差距。有人要填补这些吗?谢谢!
答案 0 :(得分:3)
运行时字符串不会保存在实习池中,因此即使它们具有相同的值,也会有不同的引用。但是,预编译的字符串保存在实习池中,并且在值相同时具有相同的引用。我倾向于认为这就是你的线程不“共享同一个锁”的原因,相同的Name
s值具有不同的引用。
答案 1 :(得分:1)
我已经构建了这个简单的类,可以在像你这样的情况下使用:
public class NamedLock : IDisposable
{
public NamedLock(string name)
{
if (name == null)
{
throw new ArgumentNullException();
}
mutex = new Mutex(false, name);
mutex.WaitOne();
}
public void Dispose()
{
if (mutex != null)
{
mutex.ReleaseMutex();
mutex = null;
}
}
private Mutex mutex;
}
用法如下:
using(new NamedLock(identity.Name))
{
// do some stuff
}
但请注意&#34;名为互斥&#34;:
因为它们是系统范围的,所以可以使用命名的互斥锁来协调跨进程边界的资源使用
关于你的问题,我也猜测你的字符串有不同的引用,而 lock语句正在处理引用相等而不是值相等,这对我来说很有意义。
答案 2 :(得分:0)
您实际需要的是原子数据库操作,而不是线程同步。
即使您根据需要进行lock
工作,它也只能在一个进程中运行。如果将应用程序扩展到多个服务器,问题将会回来。
该代码也会让其他开发人员感到困惑。没有人希望lock
用于同步对数据库表的访问。
不幸的是,简单地将当前代码包装在事务中也不起作用。现在的方式,它
如果您使用可序列化的任何事务隔离级别,则不会阻止2个事务同时插入新行。
如果你使用serializable,你将会遇到死锁,因为2个事务将获得select
的共享锁,并且他们都会尝试获得insert
的独占锁。
要解决此问题,整个操作应该按
执行在EF中,最接近1.的方法是AddOrUpdate
,但是更新&#39;部分不是你想要的。似乎唯一的选择是存储过程。
答案 3 :(得分:-1)
创建一个字典并用它来保存同步对象。
static Dictionary<string, object> lockMe
if(!lockMe.ContainsKey(identity.Name)) lockMe.Add(identity.Name,new Object());
lock(lockMe[identity.Name])
{}
编辑:你需要同步字典填充,所以把if-Add放在一个锁定语句中。