我继承了一个应用程序,它有一个服务类,可以从asp.net应用程序调用,如下所示:
class PeopleService : IPeopleService
{
//...
public void SavePerson(Person person)
{
person.UniqueId = _idGenerationService.GetNextId(person);
//...
_dbContext.InsertOrUpdate(person);
_dbContext.Commit();
}
}
class IdGenerationService : IIdGenerationService
{
//...
public string GetNextId(Person person)
{
int nextId = _dbContext.People.Count(x => x.Gender == person.Gender) + 1;
return string.Format("AB{0}", nextId);
}
}
GetNextId(Person)
的实现不是线程安全的,我们有很多具有重复ID的Person对象,如AB1,AB2,AB2等......
解决方案是将lock
应用于SavePerson(Person)
,如下所示:
class PeopleService : IPeopleService
{
private static readonly Object ReadIdLock = new Object();
//...
public void SavePerson(Person person)
{
lock(ReadIdLock)
{
person.UniqueId = _idGenerationService.GetNextId(person);
//...
_dbContext.InsertOrUpdate(person);
_dbContext.Commit();
}
}
}
现在我们尝试通过在asp.net应用程序中同时点击“保存”按钮来测试代码。但修复不起作用!有趣的是,只有第一个记录似乎是重复的,如AB1,AB1,AB2,AB3 ......
多个请求如何访问锁定的语句?我应该使用Mutex
吗?
(请注意这个例子不是生产代码,它是一个简化的代码来传达我们所遇到的问题。另外,小点,我们使用StructureMap作为DI)。
答案 0 :(得分:0)
由于lock是一个静态变量,每个appdomain只有一个这样的锁对象,它排除了很多可能的bug。这是我能想到的这个问题的唯一原因:
您的锁只能在一个应用程序域中运行。 ASP.NET可以同时在多个应用程序域中运行应用程序,例如在回收,部署或在Web园式模式下。此外,您可能有多台服务器。
最好的解决方法是使_idGenerationService.GetNextId
线程安全。可能需要对数据库查询应用适当的锁定作为此方法的基础。
另外,你锁定区域太长。您还可以将插入内容覆盖到数据库中。这会使并发性匮乏并导致分布式死锁。