1

ISession はスレッドセーフではなく、SessionFactory はスレッドセーフであることを理解しています。そのため、スレッドごとに 1 つのセッションがあることをラップして確認しました。

次の状況でエラーが発生しました。これがサポートされていないものなのか、それとも ISession スレッドの分離でまだ何かが欠けているのか疑問に思っていました。

NUnit テストを実行しています。エンティティがフィールド変数としてスタブ化されるシナリオがあります。2 つの並列タスクを実行するテストがあります。

• 各並列タスクは、同じ SessionFactory から独自のセッションを作成し、NHibernate トランザクションを開始します。
• それぞれエンティティを更新し、それに対して SaveOrUpdate を実行します。
• 次に、トランザクションをコミットして閉じます。
各タスクはこれを約 10,000 回行います。

このテスト中に、次のメッセージが表示されます。

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();
    }

テストで参照された DataAccess Manager は次のとおりです。

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

コメントに記載されているように、スレッドの安全性と競合しないセッションを確保するために考慮すべき点が 2 つあります。

  • オブジェクトがセッションによって処理されたら、Session.Evict(_configStub1) を呼び出します。このようにして、ガベージ収集されていないセッションがオブジェクトとの競合する相互作用を生成するのを防ぎます。
  • セッションにアタッチされる前にオブジェクトをロックし、このセッションから削除されるまでロックします。このようにして、オブジェクトのスレッド セーフを確保します。
于 2013-08-28T07:47:20.473 に答える
0

セッションで現在アクティブな (つまり、セッションで認識されている) エンティティ オブジェクトと対話することは、セッションと対話することと同じと見なす必要があります。セッションは異なるスレッドからの同時使用に対して安全ではないため、エンティティ オブジェクトへのアクセスもセッションによって認識されません。

これは、エンティティ インスタンスと対話すると、セッションが一部のデータを遅延ロードする可能性があり、Save() (または同様のメソッド) の呼び出し中に、NHibernate がオブジェクトを変更して ID を設定するためです (選択された ID 割り当て戦略によって異なります)。

上記は一般的なケースに当てはまります。より狭いシナリオでは、安全に実行できる可能性がありますが、その複雑さを導入する前に、そのようなことが必要であることを本当に確信する必要があると私は主張します.

于 2013-08-25T14:18:53.610 に答える