1

我知道 ISession 不是线程安全的,而 SessionFactory 是线程安全的。因此,我已经打包并确认每个线程都有一个会话。

在以下情况下,我收到一个错误,想知道这是否不受支持,或者我的 ISession 线程隔离仍然缺少某些东西。

我正在运行 NUnit 测试。我有一个场景,我的实体作为字段变量被存根。我有一个运行 2 个并行任务的测试。

• 每个并行任务从同一个SessionFactory 创建它自己的会话并开始一个NHibernate Transaction。
• 它们各自更新实体并对其执行SaveOrUpdate。
• 然后提交并关闭事务。
每个任务执行大约 10k 次。

在此测试期间,我收到一条消息:

System.AggregateException : One or more errors occurred.
  ----> NHibernate.HibernateException : identifier of an instance of Domain.Entity.MyEntity was altered from 2 to 1

这是有道理的,因为 MyEntity 是一个字段对象并被两个线程使用。因此,在 NUnit 类中创建的单个对象由两个线程引用和更新。

我的问题是这样的场景是否可以通过悲观锁定或其他 NHibernate 特性来避免?或者这只是不可行,我必须确保这种情况(即我的 Entity 对象一次不被多个线程引用和更新)永远不会在我的代码中发生?

我已经厌倦了 NHibernate 中的一些选项,比如确保实体的版本控制并尝试了一些锁定调用,但我在黑暗中通过文档猜测这是处理这种情况的正确方法,如果有的话。

编辑:感谢您的评论!这是单元测试中的代码:

private PluginConfiguration _configStub1;

    [SetUp]
    public void Setup()
    {
        new FluentMapper().Configuration().ExposeConfiguration(
            e => new SchemaExport(e).Drop(false, true)
            );

        _configStub1 = new PluginConfiguration()
            {
                Enabled = true,
                Keys = "Name",
                Value = "Fred",
                PluginName = "red",
                RuntimeId = 1
            };
     }

    [Test]
    [Explicit]
    public void HighVolume_Saves_MultiManager_SameDataRecord_SameInstance_MultiThread()
    {
        Action action1 = () =>
        {
            var dal = new DataAccessManager();
            for (int i = 0; i < 10000; i++)
            {
                dal.Begin();
                dal.Current.Session.SaveOrUpdate(_configStub1);
                dal.Current.Commit();
                dal.End();
            }
        };

        Action action2 = () =>
        {
            var dal = new DataAccessManager();
            for (int i = 0; i < 10000; i++)
            {
                dal.Begin();
                dal.Current.Session.SaveOrUpdate(_configStub1);
                dal.Current.Commit();
                dal.End();
            }
        };

        var task1 = Task.Factory.StartNew(action1);
        var task2 = Task.Factory.StartNew(action2);

        task1.Wait();
        task2.Wait();
    }

测试中引用的DataAccessManager如下:

public class DataAccessManager : IDataAccessManager
{
    private readonly ThreadLocal<ISessionManager> _current = new ThreadLocal<ISessionManager>();

    public void Begin()
    {
        Current = new SessionManager();
    }
    public ISessionManager Current
    {
        get { return _current.Value; }
        set { _current.Value = value; }
    }
    public void End(bool doComplete = true)
    {
        bool isActive = Current.Transaction != null && Current.Transaction.IsActive;

        if (doComplete && isActive) Current.Commit();
        else if (!doComplete && isActive) Current.Transaction.Rollback();

        Current.Dispose();
    }
}

SessionManager 是这样的:

public class SessionManager : ISessionManager
{
    /// <summary>
    /// Initializes a new instance of the <see cref="SessionManager"/> class.
    /// </summary>
    public SessionManager()
    {
        Session = ContextFactory.OpenSession();
        Transaction = Session.BeginTransaction();
    }

    public ITransaction Transaction { get; private set; }
    public ISession Session { get; private set; }

    public void Commit()
    {
        try
        {
            Transaction.Commit();
        }
        catch (Exception ex)
        {
            Transaction.Rollback();
            throw;
        }
    }
}
4

2 回答 2

1

如评论中所述,似乎有两点需要考虑以确保线程安全以及非冲突会话:

  • 一旦你的对象被会话处理,就调用 Session.Evict(_configStub1) 。通过这种方式,您可以防止非垃圾收集会话与您的对象产生冲突的交互。
  • 在您的对象附加到会话之前锁定您的对象,直到它被从该会话中逐出。这样,您可以确保对象的线程安全。
于 2013-08-28T07:47:20.473 回答
0

与会话中当前活动的实体对象(即会话已知)进行交互应该被视为与会话进行交互。由于会话对于来自不同线程的并发使用是不安全的,因此对会话已知的实体对象的访问也不安全。

这是因为与实体实例交互可能会导致会话延迟加载一些数据,并且在调用 Save()(或类似方法)期间,NHibernate 将改变对象以设置 id(取决于选择的身份分配策略)。

以上适用于一般情况。在更狭窄的场景中,可能可以安全地执行此操作,但我认为您应该在引入这种复杂性之前真正确定您需要这样的东西。

于 2013-08-25T14:18:53.610 回答