c# - 配列 - IDisposableオブジェクトにスレッドセーフティを追加する正しい方法は何ですか?




vb.net idisposable (4)

いくつかのパブリックメソッドを持つIDisposableインターフェイスの実装を想像してみてください。

そのタイプのインスタンスが複数のスレッド間で共有されていて、スレッドの1つがそれを破棄できる場合、他のスレッドが破棄後にそのインスタンスで動作しないようにする最良の方法は何ですか? ほとんどの場合、オブジェクトが破棄された後、そのメソッドはそれを認識し、 ObjectDisposedExceptionまたはおそらくInvalidOperationExceptionをスローするか、少なくとも何か問題が起きたことを呼び出しコードに通知する必要があります。 すべての方法 - 特に小切手が処分されている場合 - の周りに同期が必要ですか? 他のパブリックメソッドを持つすべてのIDisposable実装はスレッドセーフである必要がありますか?

次に例を示します。

public class DummyDisposable : IDisposable
{
    private bool _disposed = false;

    public void Dispose()
    {
        _disposed = true;
        // actual dispose logic
    }

    public void DoSomething()
    {
        // maybe synchronize around the if block?
        if (_disposed)
        {
            throw new ObjectDisposedException("The current instance has been disposed!");
        }

        // DoSomething logic
    }

    public void DoSomethingElse()
    {
         // Same sync logic as in DoSomething() again?
    }
}

https://code.i-harness.com


DisposeのほとんどのBCL実装はスレッドセーフではありません。 考え方は、Disposeを呼び出す前に、他の誰もインスタンスを使用していないことを確認することです。 言い換えれば、それは同期責任を上方に押し上げる。 これは理にかなっています。他のすべての消費者は、オブジェクトを使用している間にそのオブジェクトが廃棄された境界ケースを処理する必要があります。

つまり、スレッドセーフなDisposableクラスが必要な場合は、上に_disposedのチェックを付けて、すべてのパブリックメソッド(Disposeを含む)の周りにロックを作成することができます。 これは、メソッド全体のロックを保持したくない、長時間実行するメソッドがある場合には、より複雑になる可能性があります。


FWIW、サンプルコードは、私の同僚と私がこの問題をどのように処理するかを示しています。 私たちは、通常、クラスのプライベートなCheckDisposedメソッドを定義します:

private volatile bool isDisposed = false; // Set to true by Dispose

private void CheckDisposed()
{
    if (this.isDisposed)
    {
        throw new ObjectDisposedException("This instance has already been disposed.");
    }
}

次に、すべてのパブリックメソッドの先頭にあるCheckDisposed()メソッドを呼び出します。

エラー状態ではなく、処理上のスレッド競合が発生している可能性が高いと思われる場合は、公開IsDisposed()メソッドも追加しIsDisposed() Control.IsDisposedと同様)。

Update: isDisposed volatileにするという価値に関するコメントに基づいて、 CheckDisposed()メソッドの使い方を考えると、「フェンス」の問題はCheckDisposed()です。 これは、オブジェクトがすでに破棄された後にコードがパブリックメソッドを呼び出す場合を素早くキャッ​​チするためのトラブルシューティングツールです。 パブリックメソッドの開始時にCheckDisposed()を呼び出すことは、そのメソッド内にオブジェクトが配置されないことを保証するものではありません。 私が考慮しなかったエラー状態とは対照的に、クラスの設計に固有のリスクであると考えると、前述のIsDisposedメソッドと適切なロックを使用します。


あなたはあなたが処分しようとしているリソースへのアクセスをすべてロックする必要があります。 私は通常使用するDisposeパターンも追加しました。

public class MyThreadSafeClass : IDisposable
{
    private readonly object lockObj = new object();
    private MyRessource myRessource = new MyRessource();

    public void DoSomething()
    {
        Data data;
        lock (lockObj)
        {
            if (myResource == null) throw new ObjectDisposedException("");
            data = myResource.GetData();
        }
        // Do something with data
    }

    public void DoSomethingElse(Data data)
    {
        // Do something with data
        lock (lockObj)
        {
            if (myRessource == null) throw new ObjectDisposedException("");
            myRessource.SetData(data);
        }
    }

    ~MyThreadSafeClass()
    {
        Dispose(false);
    }
    public void Dispose() 
    { 
        Dispose(true); 
        GC.SuppressFinalize(this);
    }
    protected void Dispose(bool disposing) 
    {
        if (disposing)
        {
            lock (lockObj)
            {
                if (myRessource != null)
                {
                    myRessource.Dispose();
                    myRessource = null;
                }
            }
            //managed ressources
        }
        // unmanaged ressources
    }
}

整数型のオブジェクトの「配置済み」変数または「状態」変数で、整数とInterlocked.ExchangeまたはInterlocked.CompareExchangeを使用することをおInterlocked.CompareExchangeします。 Interlocked.ExchangeInterlocked.CompareExchangeがそのような型を扱うことができるのであれば、私はenum使うでしょうが、そうではありません。

IDisposable.Dispose()が進行中にオブジェクトのファイナライザが実行されないうちに、その型のオブジェクトが死んでいると宣言されないようにする方法はありません復活した。 確かに、もし外部コードがそれが起こることを許すならば、オブジェクトが "正常に動作する"ことは明らかにできませんが、Disposeとfinalizeメソッドは他のオブジェクトを破壊しないように十分に保護されていなければなりません'状態であり、これは一般に、オブジェクト状態変数に対するロックまたはInterlocked操作の使用を一般的に必要とする。





idisposable