NHibernate 数据在多线程应用程序中不同步

NHibernate data is out of sync in a multi-threaded application

我在多线程场景中使用 NHibernate。这是一个执行以下操作的测试。

  1. (启动多线程服务器。)
  2. 向服务器发送请求。服务器创建一个状态为 A 的新 Foo 对象,并使用 NHibernate 将其存储在数据库中。
  3. 为新创建的 Foo 对象设置状态 B
  4. 向服务器发送请求。服务器使用 NHibernate 从数据库中获取 Foo,读取其状态(应为 B)并相应地执行操作。
  5. (停止服务器。)

现在我的问题是偶尔(在 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 创建一个新会话。