24

IDisposableインターフェイスを実装するクラスがあるとします。このようなもの:

http://www.flickr.com/photos/garthof/3149605015/

MyClassはいくつかのアンマネージ リソースを使用するため、IDisposableのDispose()メソッドはそれらのリソースを解放します。MyClassは次のように使用する必要があります。

using ( MyClass myClass = new MyClass() ) {
    myClass.DoSomething();
}

ここで、 DoSomething()を非同期的に呼び出すメソッドを実装したいと考えています。MyClassに新しいメソッドを追加します。

http://www.flickr.com/photos/garthof/3149605005/

ここで、クライアント側から、MyClassを次のように使用する必要があります。

using ( MyClass myClass = new MyClass() ) {
    myClass.AsyncDoSomething();
}

ただし、他に何もしないと、DoSomething()が呼び出される前にオブジェクトmyClassが破棄される可能性があるため、これは失敗する可能性があります(予期しないObjectDisposedExceptionがスローされます)。そのため、 DoSomething()への非同期呼び出しが完了するまで、 Dispose()メソッドへの呼び出し(暗黙的または明示的) を遅らせる必要があります。

Dispose()メソッドのコードは非同期で実行する必要があり、すべての非同期呼び出しが解決されるのは一度だけだと思います。これを達成するための最良の方法を知りたいです。

ありがとう。

注: 簡単にするために、Dispose() メソッドの実装方法の詳細には触れていません。実生活では、私は通常Dispose パターンに従います。


更新:返信ありがとうございます。あなたの努力に感謝します。chakrit がコメントしたように、非同期 DoSomething への複数の呼び出しを行うことができる必要があります。理想的には、次のようなものがうまく機能するはずです。

using ( MyClass myClass = new MyClass() ) {

    myClass.AsyncDoSomething();
    myClass.AsyncDoSomething();

}

カウンティング セマフォを調べてみます。探しているものと思われます。また、設計上の問題である可能性もあります。都合がよければ、実際のケースとMyClassが実際に行っていることをいくつか紹介します。

4

9 に答える 9

11

イベントベースの非同期パターンを使用しているようです(.NET非同期パターンの詳細については、こちらを参照してください)。したがって、通常は、非同期操作が完了したときに発生するクラス上のイベントが指定されますDoSomethingCompleted(注意してください)。パターンに正しく従うためにAsyncDoSomething実際に呼び出す必要があります)。DoSomethingAsyncこのイベントが公開されると、次のように書くことができます。

var myClass = new MyClass();
myClass.DoSomethingCompleted += (sender, e) => myClass.Dispose();
myClass.DoSomethingAsync();

もう1つの方法は、パターンを使用することです。このパターンIAsyncResultでは、disposeメソッドを呼び出すデリゲートをAsyncCallbackパラメーターに渡すことができます(このパターンの詳細については、上のページも参照してください)。この場合、の代わりにメソッドがありBeginDoSomething、次のように呼ばれます...EndDoSomethingDoSomethingAsync

var myClass = new MyClass();
myClass.BeginDoSomething(
    asyncResult => {
                       using (myClass)
                       {
                           myClass.EndDoSomething(asyncResult);
                       }
                   },
    null);        

ただし、どちらの方法でも、非同期操作が完了したことを呼び出し元に通知して、オブジェクトを正しいタイミングで破棄できるようにする方法が必要です。

于 2008-12-30T12:28:43.843 に答える
5

通常、非同期メソッドにはコールバックがあり、完了時に何らかのアクションを実行できます。これがあなたの場合であれば、次のようになります。

// The async method taks an on-completed callback delegate
myClass.AsyncDoSomething(delegate { myClass.Dispose(); });

これを回避する別の方法は、非同期ラッパーです。

ThreadPool.QueueUserWorkItem(delegate
{
    using(myClass)
    {
        // The class doesn't know about async operations, a helper method does that
        myClass.DoSomething();
    }
});
于 2008-12-30T12:23:08.317 に答える
3

IDisposableオブジェクトの作成がそれがあったスレッドコンテキストの継続的な存在を強制することができる正しい方法がないので、Microsoftが契約の一部として実装がDispose任意のスレッドコンテキストから呼び出されることを許可することを要求しなかったことは残念だと思います作成した。オブジェクトを作成するスレッドが、オブジェクトが陳腐化するのをなんとかして監視しDispose、都合のよいときにできるようにコードを設計することができます。また、スレッドが他の何かに必要でなくなったときに、適切なオブジェクトがすべてなくなるまで、スレッドが残ります。Disposedですが、スレッドを作成する部分で特別な動作を必要としない標準的なメカニズムはないと思いますDispose

最善の策は、おそらくすべての対象オブジェクトを共通のスレッド(おそらくUIスレッド)内に作成し、対象オブジェクトの存続期間中スレッドが存続することを保証し、オブジェクトControl.BeginInvokeを要求するようなものを使用することです。 '処分。オブジェクトの作成もクリーンアップもブロックされない場合、それは良いアプローチかもしれませんが、どちらかの操作がブロックされる可能性がある場合は、別のアプローチが必要になる可能性があります[おそらく、独自のスレッドで非表示のダミーフォームを開くので、そこで使用Control.BeginInvoke]。

または、実装を制御できる場合は、IDisposable非同期で安全に起動できるように実装を設計します。多くの場合、廃棄時に誰もそのアイテムを使おうとしない限り、それは「うまくいく」でしょうが、それはほとんど与えられていません。特に、多くのタイプではIDisposable、複数のオブジェクトインスタンスが共通の外部リソースを操作する可能性があるという本当の危険があります[たとえば、オブジェクトは作成されたインスタンスを保持しList<>、作成時にそのリストにインスタンスを追加し、Dispose;上のインスタンスを削除する可能性があります。リスト操作が同期されていない場合、破棄されているオブジェクトが他の方法で使用されていなくても、非同期Disposeによってリストが破損する可能性があります。

ところで、便利なパターンは、オブジェクトが使用中に非同期で破棄できるようにすることです。このような破棄により、進行中の操作が最初の都合の良い機会に例外をスローすることが予想されます。ソケットのようなものはこのように機能します。ソケットを役に立たない状態のままにせずに読み取り操作を早期に終了することはできない場合がありますが、ソケットが使用されない場合、別のスレッドが次のように判断した場合、読み取りがデータを待機し続ける意味はありません。それはあきらめる必要があります。私見、それはすべてのIDisposableオブジェクトが振る舞うように努力するべき方法です、しかし私はそのような一般的なパターンを要求する文書を知りません。

于 2013-01-19T18:45:52.997 に答える
2

非同期処理を可能にするために、なんらかの方法でコードを変更することはありません。代わりに、AsyncDoSomethingが呼び出されたときに、実行する必要のあるすべてのデータのコピーが含まれていることを確認します。そのメソッドは、そのリソースがあればすべてをクリーンアップする責任があります。

于 2008-12-30T12:26:22.757 に答える
2

コールバックメカニズムを追加して、クリーンアップ関数をコールバックとして渡すことができます。

var x = new MyClass();

Action cleanup = () => x.Dispose();

x.DoSomethingAsync(/*and then*/cleanup);

ただし、同じオブジェクトインスタンスから複数の非同期呼び出しを実行する場合は、これにより問題が発生します。

1つの方法は、Semaphoreクラスを使用して単純なカウントセマフォを実装し、実行中の非同期ジョブの数をカウントすることです。

MyClassにカウンターを追加し、すべてのAsyncWhatever呼び出しでカウンターをインクリメントし、終了時にカウンターをデセレーションします。セマフォが0の場合、クラスを破棄する準備ができています。

var x = new MyClass();

x.DoSomethingAsync();
x.DoSomethingAsync2();

while (x.RunningJobsCount > 0)
    Thread.CurrentThread.Sleep(500);

x.Dispose();

しかし、それが理想的な方法だとは思えません。デザインの問題の匂いがします。たぶん、MyClassの設計を再考することで、これを回避できるでしょうか。

MyClassの実装について少し教えていただけますか?それは何をすることになっていますか?

于 2008-12-30T12:35:38.730 に答える
1

したがって、私の考えは、完了を保留しているAsyncDoSomething()の数を保持し、このカウントがゼロに達したときにのみ破棄することです。私の最初のアプローチは次のとおりです。

public class MyClass : IDisposable {

    private delegate void AsyncDoSomethingCaller();
    private delegate void AsyncDoDisposeCaller();

    private int pendingTasks = 0;

    public DoSomething() {
        // Do whatever.
    }

    public AsyncDoSomething() {
        pendingTasks++;
        AsyncDoSomethingCaller caller = new AsyncDoSomethingCaller();
        caller.BeginInvoke( new AsyncCallback( EndDoSomethingCallback ), caller);
    }

    public Dispose() {
        AsyncDoDisposeCaller caller = new AsyncDoDisposeCaller();
        caller.BeginInvoke( new AsyncCallback( EndDoDisposeCallback ), caller);
    }

    private DoDispose() {
        WaitForPendingTasks();

        // Finally, dispose whatever managed and unmanaged resources.
    }

    private void WaitForPendingTasks() {
        while ( true ) {
            // Check if there is a pending task.
            if ( pendingTasks == 0 ) {
                return;
            }

            // Allow other threads to execute.
            Thread.Sleep( 0 );
        }
    }

    private void EndDoSomethingCallback( IAsyncResult ar ) {
        AsyncDoSomethingCaller caller = (AsyncDoSomethingCaller) ar.AsyncState;
        caller.EndInvoke( ar );
        pendingTasks--;
    }

    private void EndDoDisposeCallback( IAsyncResult ar ) {
        AsyncDoDisposeCaller caller = (AsyncDoDisposeCaller) ar.AsyncState;
        caller.EndInvoke( ar );
    }
}

2つ以上のスレッドがpendingTasks変数を同時に読み取り/書き込みしようとすると、いくつかの問題が発生する可能性があるため、競合状態を防ぐためにlockキーワードを使用する必要があります。

public class MyClass : IDisposable {

    private delegate void AsyncDoSomethingCaller();
    private delegate void AsyncDoDisposeCaller();

    private int pendingTasks = 0;
    private readonly object lockObj = new object();

    public DoSomething() {
        // Do whatever.
    }

    public AsyncDoSomething() {
        lock ( lockObj ) {
            pendingTasks++;
            AsyncDoSomethingCaller caller = new AsyncDoSomethingCaller();
            caller.BeginInvoke( new AsyncCallback( EndDoSomethingCallback ), caller);
        }
    }

    public Dispose() {
        AsyncDoDisposeCaller caller = new AsyncDoDisposeCaller();
        caller.BeginInvoke( new AsyncCallback( EndDoDisposeCallback ), caller);
    }

    private DoDispose() {
        WaitForPendingTasks();

        // Finally, dispose whatever managed and unmanaged resources.
    }

    private void WaitForPendingTasks() {
        while ( true ) {
            // Check if there is a pending task.
            lock ( lockObj ) {
                if ( pendingTasks == 0 ) {
                    return;
                }
            }

            // Allow other threads to execute.
            Thread.Sleep( 0 );
        }
    }

    private void EndDoSomethingCallback( IAsyncResult ar ) {
        lock ( lockObj ) {
            AsyncDoSomethingCaller caller = (AsyncDoSomethingCaller) ar.AsyncState;
            caller.EndInvoke( ar );
            pendingTasks--;
        }
    }

    private void EndDoDisposeCallback( IAsyncResult ar ) {
        AsyncDoDisposeCaller caller = (AsyncDoDisposeCaller) ar.AsyncState;
        caller.EndInvoke( ar );
    }
}

このアプローチには問題があります。リソースの解放は非同期で行われるため、次のように機能する可能性があります。

MyClass myClass;

using ( myClass = new MyClass() ) {
    myClass.AsyncDoSomething();
}

myClass.DoSomething();

DoSomething()がusing句の外部で呼び出されたときに、期待される動作がObjectDisposedExceptionを起動する必要がある場合。しかし、私はこの解決策を再考するのに十分なほど悪いとは思いません。

于 2008-12-30T12:46:48.020 に答える