【问题标题】:NHibernate data is out of sync in a multi-threaded applicationNHibernate 数据在多线程应用程序中不同步
【发布时间】:2015-07-20 18:34:08
【问题描述】:

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

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

现在我的问题是,在第 4 步中偶尔(在 1 - 2% 的情况下)服务器会读取状态 A,而它应该读取 B 并且测试失败。 我在这里做错了什么?

NHibernate 会话通过设置为 thread staticCurrentSessionContext 检索。我使用 Fluent NHibernate,但我想这并不重要。

编辑

关于服务器的详细信息: 服务器是一个简单的 TCP 服务器。启动时,它会创建一个新的 thread task 来等待传入的连接。当一个新连接被接受时,它会创建另一个 thread task 来处理来自连接的请求并发送响应。处理thread task 终止。

服务器使用Task.Factory.StartNew() 创建新的线程 任务

编辑 2

我编写了一个小型 NUnit 测试来重现此行为。它失败了

预期:打开但被:初始化

在第一次迭代中。

[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;
}

我认为映射关系不大。

【问题讨论】:

  • 我认为你必须更详细地展示你正在做的事情,以便任何人弄清楚它有什么问题。
  • 另外,你说的是“多线程”,但不清楚这些多线程在哪里或如何涉及。
  • @OskarBerggren 我试图描述它是因为很难将整个应用程序简化为几行代码。我还提到了多线程服务器,因为我认为它与会话或状态的同步有关。
  • 嗯,这里很可能存在某种竞争条件,但从如此高级别的描述中找不到。您同时有多少个请求?即使所有请求都按顺序而不是同时运行,您是否看到问题?您如何管理会话(生命周期)?缓存?等等……
  • 查看我的测试。我希望这比我的高级描述更好。

标签: c# multithreading nhibernate fluent-nhibernate


【解决方案1】:

在所示的单元测试中,session3 将与 session1 相同,因此 transaction3 将与 transaction1 是同一对象实例,因此它仍将显示添加事务对象时的值。

第三部分的查询将命中数据库,但 NHibernate 会看到获取的对象已经加载并返回会话已经在内存中保存的实例。它通常不会覆盖对象中的值。

【讨论】:

  • 好吧,这有点道理。您对此有何建议?每次从数据库加载后,我应该使用.Refresh() 吗?你看到我的CacheMode 配置了吗?我认为这会导致 NHibernate 忽略那些对象 "hold in memory"?
  • 顺便说一句:session3.Refresh(transaction3); 将导致测试通过!
  • @DioF 你到底想在这里完成什么?编写测试用例是真正的问题,还是只是真正问题的简化模型?因为解决方案可能不同。在测试用例中,您可以使用 Refresh 或确保第 1 部分和第 3 部分使用不同的会话。顺便说一句,循环的所有迭代都将为 session1 和 session3 使用相同的会话实例(会话总数将为 (1+100)。这真的是您想要测试的吗?我假设生产代码看起来会有所不同,因此解决方案可能会有所不同。
  • 不用查,我相信CacheMode是关于二级缓存的。一级缓存始终存在(除非您使用无状态会话),因为它是在会话中管理对象身份的功能(简单地说,数据库中的每一行应最多对应于内存中的一个对象(每个会话)),并且它还用于检测需要持久化的任何更改。
  • 是的。我假设会话在同步方面会更聪明。但现在我更清楚会话做了什么,不做什么。
【解决方案2】:

在服务器中,一般做法是为每个请求创建一个会话,并在请求开始时启动事务。当请求即将完成时,事务要么提交,要么回滚,具体取决于结果。

在您的测试中,它实际上并没有模拟这种场景,它对请求 1 和请求 3 使用相同的会话,而在实际场景中永远不会出现这种情况。

因此,为了测试实际场景,您可以为会话 3 创建一个新会话。

【讨论】:

  • 你是对的。事实上我正在这样做——或者至少我是这么想的。这偶尔发生的问题是我创建了一个Task而不是Thread。所以Task 不一定会产生一个新的Thread,这就是为什么偶尔Task 使用已经存在的会话在主Thread 中运行,该会话使用状态。现在我使用new Thread().Start(),它总是通过。
猜你喜欢
  • 2015-02-06
  • 2018-12-17
  • 2012-10-22
  • 1970-01-01
  • 1970-01-01
  • 2011-07-16
  • 1970-01-01
  • 2015-02-28
  • 1970-01-01
相关资源
最近更新 更多