NHibernate 数据在多线程应用程序中不同步
NHibernate data is out of sync in a multi-threaded application
我在多线程场景中使用 NHibernate。这是一个执行以下操作的测试。
- (启动多线程服务器。)
- 向服务器发送请求。服务器创建一个状态为
A
的新 Foo
对象,并使用 NHibernate 将其存储在数据库中。
- 为新创建的
Foo
对象设置状态 B
。
- 向服务器发送请求。服务器使用 NHibernate 从数据库中获取
Foo
,读取其状态(应为 B
)并相应地执行操作。
- (停止服务器。)
现在我的问题是偶尔(在 1 - 2% 的情况下)在步骤 4 中服务器读取状态 A
而它应该读取 B
并且测试失败。 我做错了什么?
NHibernate 会话通过 CurrentSessionContext
检索,设置为 线程静态。我使用 Fluent NHibernate,但我想这无关紧要。
编辑
关于服务器的详细信息:
服务器是一个简单的 TCP 服务器。启动时,它会创建一个新的 thread task 来等待传入连接。当接受新连接时,它会创建另一个 thread task 来处理来自连接的请求并发送响应。处理线程任务终止。
服务器使用Task.Factory.StartNew()
创建新的线程任务。
编辑 2
我写了一个小的 NUnit 测试来重现这个行为。它失败了
Expected: Open But was: Initialized
第一次迭代。
[Test]
[Explicit]
public void NHibernateProblem()
{
for (var i = 0; i < 100; i++)
{
var identifier = Guid.NewGuid().ToString();
var session1 = Database.GetCurrentSession();
using (var dbTransaction = session1.BeginTransaction())
{
var transaction1 = new Transaction
{
Identifier = identifier,
Status = TransactionStatus.Initialized
};
session1.SaveOrUpdate(transaction1);
dbTransaction.Commit();
}
Task.Factory.StartNew(() =>
{
var session2 = Database.GetCurrentSession();
using (var dbTransaction = session2.BeginTransaction())
{
var transaction2 = session2
.QueryOver<Transaction>()
.Where(t => t.Identifier == identifier)
.SingleOrDefault();
transaction2.Status = TransactionStatus.Open;
session2.SaveOrUpdate(transaction2);
dbTransaction.Commit();
}
}).Wait();
var session3 = Database.GetCurrentSession();
using (var dbTransaction = session3.BeginTransaction())
{
var transaction3 = session3
.QueryOver<Transaction>()
.Where(t => t.Identifier == identifier)
.SingleOrDefault();
dbTransaction.Commit();
Console.WriteLine("iteration {0}", i);
Assert.That(transaction3.Status, Is.EqualTo(TransactionStatus.Open));
}
}
}
Database.GetCurrentSession()
看起来像这样:
public virtual ISession GetCurrentSession()
{
if (CurrentSessionContext.HasBind(SessionFactory))
{
var currentSession = SessionFactory.GetCurrentSession();
return currentSession;
}
var session = SessionFactory.OpenSession();
session.FlushMode = FlushMode.Always;
session.CacheMode = CacheMode.Ignore;
CurrentSessionContext.Bind(session);
return session;
}
我认为映射不太相关。
在所示的单元测试中,session3 将与 session1 相同,因此 transaction3 将与 transaction1 是同一对象实例,因此它仍将显示添加 transaction-object 时的值。
第三部分中的查询将访问数据库,但是 NHibernate 会看到获取的对象已经加载并且 return 会话已经保存在内存中的实例。它通常不会覆盖对象中的值。
在服务器中,一般做法是为每个请求创建一个会话,并在请求开始时启动事务。当请求即将完成时,根据结果提交或回滚事务。
在您的测试中,它实际上并没有模拟这种情况,它对请求 1 和请求 3 使用相同的会话,而在实际情况中永远不会出现这种情况。
因此,为了测试实际场景,您可以为会话 3 创建一个新会话。
我在多线程场景中使用 NHibernate。这是一个执行以下操作的测试。
- (启动多线程服务器。)
- 向服务器发送请求。服务器创建一个状态为
A
的新Foo
对象,并使用 NHibernate 将其存储在数据库中。 - 为新创建的
Foo
对象设置状态B
。 - 向服务器发送请求。服务器使用 NHibernate 从数据库中获取
Foo
,读取其状态(应为B
)并相应地执行操作。 - (停止服务器。)
现在我的问题是偶尔(在 1 - 2% 的情况下)在步骤 4 中服务器读取状态 A
而它应该读取 B
并且测试失败。 我做错了什么?
NHibernate 会话通过 CurrentSessionContext
检索,设置为 线程静态。我使用 Fluent NHibernate,但我想这无关紧要。
编辑
关于服务器的详细信息:
服务器是一个简单的 TCP 服务器。启动时,它会创建一个新的 thread task 来等待传入连接。当接受新连接时,它会创建另一个 thread task 来处理来自连接的请求并发送响应。处理线程任务终止。
服务器使用Task.Factory.StartNew()
创建新的线程任务。
编辑 2
我写了一个小的 NUnit 测试来重现这个行为。它失败了
Expected: Open But was: Initialized
第一次迭代。
[Test]
[Explicit]
public void NHibernateProblem()
{
for (var i = 0; i < 100; i++)
{
var identifier = Guid.NewGuid().ToString();
var session1 = Database.GetCurrentSession();
using (var dbTransaction = session1.BeginTransaction())
{
var transaction1 = new Transaction
{
Identifier = identifier,
Status = TransactionStatus.Initialized
};
session1.SaveOrUpdate(transaction1);
dbTransaction.Commit();
}
Task.Factory.StartNew(() =>
{
var session2 = Database.GetCurrentSession();
using (var dbTransaction = session2.BeginTransaction())
{
var transaction2 = session2
.QueryOver<Transaction>()
.Where(t => t.Identifier == identifier)
.SingleOrDefault();
transaction2.Status = TransactionStatus.Open;
session2.SaveOrUpdate(transaction2);
dbTransaction.Commit();
}
}).Wait();
var session3 = Database.GetCurrentSession();
using (var dbTransaction = session3.BeginTransaction())
{
var transaction3 = session3
.QueryOver<Transaction>()
.Where(t => t.Identifier == identifier)
.SingleOrDefault();
dbTransaction.Commit();
Console.WriteLine("iteration {0}", i);
Assert.That(transaction3.Status, Is.EqualTo(TransactionStatus.Open));
}
}
}
Database.GetCurrentSession()
看起来像这样:
public virtual ISession GetCurrentSession()
{
if (CurrentSessionContext.HasBind(SessionFactory))
{
var currentSession = SessionFactory.GetCurrentSession();
return currentSession;
}
var session = SessionFactory.OpenSession();
session.FlushMode = FlushMode.Always;
session.CacheMode = CacheMode.Ignore;
CurrentSessionContext.Bind(session);
return session;
}
我认为映射不太相关。
在所示的单元测试中,session3 将与 session1 相同,因此 transaction3 将与 transaction1 是同一对象实例,因此它仍将显示添加 transaction-object 时的值。
第三部分中的查询将访问数据库,但是 NHibernate 会看到获取的对象已经加载并且 return 会话已经保存在内存中的实例。它通常不会覆盖对象中的值。
在服务器中,一般做法是为每个请求创建一个会话,并在请求开始时启动事务。当请求即将完成时,根据结果提交或回滚事务。
在您的测试中,它实际上并没有模拟这种情况,它对请求 1 和请求 3 使用相同的会话,而在实际情况中永远不会出现这种情况。
因此,为了测试实际场景,您可以为会话 3 创建一个新会话。