众所周知,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 中运行。
所以所有线程都必须使用相同的会话。做这个的最好方式是什么?
在初始线程(线程安全实现)中使用保存队列,该队列从主线程循环轮询(而不是 EndInvoke())。子线程可以插入 NHibernate 对象以由主线程保存。
使用一些回调机制在主线程中保存/刷新对象。WPF、Control.Invoke() 或 BackgroundWorker 中是否存在类似于 UI 线程回调的可能?
将保存/刷新访问放入锁定(会话)块?可能很危险,因为修改 NHibernate 对象可能会更改会话,即使不执行 Save()/Flush()。
或者我应该忍受数据库开销来为每个线程中的单独会话加载相同的对象,在主线程中驱逐并重新加载它们,然后再次进行更改?[编辑:由于对象并发/陈旧对象的风险而导致的错误“解决方案”]
还要考虑到应用程序在 NHibernate 之上有一个业务逻辑层,它有类似的对象,但是通过它自己的 Save() 命令将它的属性值发送到 NHibernate 对象,然后修改它们并立即执行 NHibernate Save()/Flush() .
编辑:对 NHibernate 对象的任何读取操作都可能会更改会话,这一点很重要——延迟加载、儿童收集在某些条件下会发生变化。所以最好在上面有一个业务对象层,它可以同步所有对 NHibernate 对象的访问。考虑到数据库操作只占用最少的线程时间(主要是偶尔的状态设置),大部分用于计算、监视、Web服务访问等,数据层同步的性能损失可以忽略不计。