1

众所周知,NHibernate 会话不是线程安全的。但是我们在几个长时间运行的线程中分割了一个代码路径,所有线程都使用在初始线程中加载的对象。

using (var session = factory.OpenSession())
{
    var parent = session.Get<T>(parentId);
    DoSthWithParent(session, parent);
    foreach (var child in parent.children)
    {
        parallelThreadMethodLongRunning.BeginInvoke(session, child);
        //[Thread #1] DoSthWithChild(child #1) -> SaveOrUpdate(child #1) + Flush()
        //[Thread #2] DoSthWithChild(child #2) -> SaveOrUpdate(child #2) + Flush()
        //[Thread #3] DoSthWithChild(child #3) -> SaveOrUpdate(child #3) + Flush()
        // -> etc... changes to be persisted immediately, not all at the end.
        EndInvoke();
    }
    DoFinalChangesOnParentAndChildren(parent);
    session.Flush();
}

}

一种方法是为每个线程创建一个会话,但这需要在每个线程中重新加载父对象。另外,最后一个方法也在对孩子进行更改,如果另一个会话同时更改它,或者必须被驱逐/重新加载,它将在 StaleObjectException 中运行。

所以所有线程都必须使用相同的会话。做这个的最好方式是什么?

  1. 在初始线程(线程安全实现)中使用保存队列,该队列从主线程循环轮询(而不是 EndInvoke())。子线程可以插入 NHibernate 对象以由主线程保存。

  2. 使用一些回调机制在主线程中保存/刷新对象。WPF、Control.Invoke() 或 BackgroundWorker 中是否存在类似于 UI 线程回调的可能?

  3. 将保存/刷新访问放入锁定(会话)块?可能很危险,因为修改 NHibernate 对象可能会更改会话,即使不执行 Save()/Flush()。

  4. 或者我应该忍受数据库开销来为每个线程中的单独会话加载相同的对象,在主线程中驱逐并重新加载它们,然后再次进行更改?[编辑:由于对象并发/陈旧对象的风险而导致的错误“解决方案”]

还要考虑到应用程序在 NHibernate 之上有一个业务逻辑层,它有类似的对象,但是通过它自己的 Save() 命令将它的属性值发送到 NHibernate 对象,然后修改它们并立即执行 NHibernate Save()/Flush() .

编辑:对 NHibernate 对象的任何读取操作都可能会更改会话,这一点很重要——延迟加载、儿童收集在某些条件下会发生变化。所以最好在上面有一个业务对象层,它可以同步所有对 NHibernate 对象的访问。考虑到数据库操作只占用最少的线程时间(主要是偶尔的状态设置),大部分用于计算、监视、Web服务访问等,数据层同步的性能损失可以忽略不计。

4

2 回答 2

0

session 对象不是线程安全的,你不能在不同的线程上使用它。单独线程中的 SaveOrUpdate 很可能会使您的程序崩溃或损坏您的数据库。但是,如何创建要更新的数据集并在主线程中执行 SaveOrUpdate 操作(是否创建了会话)?

在创建 NHibernate 会话时,您应该遵守以下做法: • 切勿为每个数据库连接创建多个并发 ISession 或 ITransaction 实例。

• 在为每个数据库的每个事务创建多个 ISession 时要格外小心。ISession 本身会跟踪对加载对象所做的更新,因此不同的 ISession 可能会看到陈旧的数据。

• ISession 不是线程安全的!永远不要在两个并发线程中访问同一个 ISession。ISession 通常只是一个单一的工作单元!

于 2013-03-21T09:59:16.057 回答
0

首先,如果我理解正确,不同的线程可能会更新相同的对象。在这种情况下,无论是否使用 nHibernate,您都会同时对同一对象执行多个更新,这可能会导致意外结果。
您可能需要稍微调整您的设计以确保对象只能由(最多)单个线程更新。

现在,假设您的流程可能包括让相同的线程读取相同的数据(但写入不同的数据),我建议使用不同的会话 - 每个线程一个,并使用2nd level cache
二级缓存保持在SessionFactory(而不是Session)级别,因此由所有会话实例共享。

于 2013-03-24T20:02:25.593 回答