使用字符串

时间:2015-11-04 17:48:23

标签: c# .net locking

我有一些代码在给定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在两个线程中都是相同的:

enter image description here

我有点倾向于认为lock关键字适用于精确的引用相等,这是有意义的。但是,在MSDN上挖掘lock documentation时,它明确地说:

enter image description here

我假设框架有一个锁的哈希并将检查对象是否相等。它也可能是文档具有误导性,因为它使用了一个字符串常量来实现。

无论哪种方式,很明显我对锁的理解存在一些差距。有人要填补这些吗?谢谢!

4 个答案:

答案 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用于同步对数据库表的访问。

不幸的是,简单地将当前代码包装在事务中也不起作用。现在的方式,它

  1. 从表格中选择一行
  2. 如果未找到任何行,请插入新行
  3. 如果您使用可序列化的任何事务隔离级别,则不会阻止2个事务同时插入新行。

    如果你使用serializable,你将会遇到死锁,因为2个事务将获得select的共享锁,并且他们都会尝试获得insert的独占锁。

    要解决此问题,整个操作应该按

    执行
    1. 如果新行不存在则插入新行
    2. 选择行
    3. 在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放在一个锁定语句中。