首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >锁关键字不能按预期使用字符串。

锁关键字不能按预期使用字符串。
EN

Stack Overflow用户
提问于 2015-11-04 17:48:23
回答 4查看 187关注 0票数 1

我有一些代码可以在具有给定token的数据库中创建一个新行,或者如果现有行已经存在,则返回该行:

代码语言:javascript
运行
复制
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语句:

代码语言:javascript
运行
复制
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,所以我真正想做的是阻止同一个用户调用该方法,但允许两个不同的用户同时调用该方法。我应该能够锁定用户的身份:

代码语言:javascript
运行
复制
lock (identity.Name) // This is a string
{
   // Same stuff here

   return newRun;
}

然而,这是行不通的。我已经在调试器中完成了它,实际上它只是直接遍历lock,尽管identity.Name在两个线程中都是相同的:

我有点倾向于相信lock关键字可以实现精确的引用相等,这是有意义的。然而,在MSDN上的锁文件中,它清楚地指出了相反的情况:

我假设框架有一个哈希锁,并将检查对象相等。它也可能是文档误导性的,因为它使用的是一个字符串常量,而这个常量将被嵌入。

不管怎样,很明显,我对锁的理解有一些差距。有人要填这些吗?谢谢!

EN

回答 4

Stack Overflow用户

发布于 2015-11-04 18:06:12

运行时字符串不会保存在实习生池中,因此即使它们具有相同的值,也有不同的引用。但是,预编译字符串保存在实习生池中,并且在值相同时具有相同的引用。我倾向于相信这就是为什么您的线程没有“共享相同的锁”,相同的Name值有不同的引用。

票数 3
EN

Stack Overflow用户

发布于 2015-11-04 18:06:39

我构建了这个简单的类,用于像您这样的情况:

代码语言:javascript
运行
复制
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;
}

用法如下:

代码语言:javascript
运行
复制
using(new NamedLock(identity.Name))
{
    // do some stuff
}

但是要小心“命名互斥”:

因为它们是系统范围的,所以可以使用命名互斥来协调跨进程边界的资源使用。

关于您的问题,我还猜测您的字符串有不同的引用,锁语句处理的是引用相等而不是值相等,这对我来说是有意义的。

票数 1
EN

Stack Overflow用户

发布于 2015-11-04 22:48:38

实际上您要寻找的是原子数据库操作,而不是线程同步。

即使您让lock按您的需要工作,它也只能在一个进程中工作。如果将应用程序扩展到多个服务器,问题就会再次出现。

这些代码也会让其他开发人员感到困惑。没有人期望使用lock来同步对数据库表的访问。

不幸的是,简单地将当前代码包装在事务中也是行不通的。它现在的写法,它

  1. 从表中选择一行
  2. 如果没有找到行,则插入一个新行。

如果使用可序列化以下的任何事务隔离级别,则不会阻止2个事务同时插入新行。

如果您使用可串行化,您将得到死锁,因为两个事务将为select获取一个共享锁,并且这两个事务都将尝试为insert获取一个独占锁。

为了解决这个问题,整个操作应该执行如下:

  1. 如果新行不存在,则插入它
  2. 选择行

在EF中,与1最接近的方法是AddOrUpdate,但“update”部分不是您想要的。这样做的唯一选择似乎是存储过程。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/33528651

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档