質問
IDisposable インターフェースを実装するクラスがあるとしましょう。このようなもの:
MyClass は管理されていないリソースを使用するため、 IDisposable の Dispose()メソッドはそれらのリソースを解放します。 MyClass は次のように使用する必要があります。
using ( MyClass myClass = new MyClass() ) {
myClass.DoSomething();
}
今、非同期で DoSomething()を呼び出すメソッドを実装します。 MyClass に新しいメソッドを追加します:
今、クライアント側から、 MyClass を次のように使用する必要があります。
using ( MyClass myClass = new MyClass() ) {
myClass.AsyncDoSomething();
}
ただし、他に何もしないと、 DoSomething()が呼び出される前に myClass オブジェクトが破棄される可能性があるため、これが失敗する可能性があります(予期しない< strong> ObjectDisposedException )。そのため、 DoSomething()の非同期呼び出しが完了するまで、 Dispose()メソッドの呼び出し(暗黙的または明示的)を遅らせる必要があります。
Dispose()メソッドのコードは、非同期的に、すべての非同期呼び出しが解決された後にのみ実行する必要があると思います。これを達成する最良の方法はどれかを知りたい。
ありがとう。
注:簡単にするために、Dispose()メソッドの実装方法の詳細は入力していません。実際には、通常破棄パターンに従います。
更新:ご回答ありがとうございます。あなたの努力に感謝します。 chakrit コメントがあるので、非同期DoSomethingを複数回呼び出すことができる必要があります。理想的には、このようなものはうまくいくはずです:
using ( MyClass myClass = new MyClass() ) {
myClass.AsyncDoSomething();
myClass.AsyncDoSomething();
}
カウントセマフォを調べます。探しているもののようです。また、設計上の問題かもしれません。便利だと思う場合は、実際のケースの一部と MyClass が実際に行うことについて説明します。
解決 6
したがって、私の考えは、完了まで保留されている 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();
using 句の外で DoSomething()が呼び出されたときに、予想される動作が ObjectDisposedException を起動する場合。しかし、この解決策を再考するほど悪いとは思いません。
他のヒント
イベントベースの非同期パターンを使用しているようです(こちらをご覧ください.NET非同期パターンの詳細については、)を参照してください。通常は、DoSomethingCompleted
という名前の非同期操作が完了したときに起動するクラスのイベントです(AsyncDoSomething
は実際に<= >正しくパターンをたどります)。このイベントを公開すると、次のように記述できます。
var myClass = new MyClass();
myClass.DoSomethingCompleted += (sender, e) => myClass.Dispose();
myClass.DoSomethingAsync();
他の代替方法は、DoSomethingAsync
パターンを使用することです。ここでは、disposeメソッドを呼び出すデリゲートをIAsyncResult
パラメーターに渡すことができます(このパターンの詳細については、上記のページでも説明しています)。この場合、AsyncCallback
の代わりにBeginDoSomething
およびEndDoSomething
メソッドがあり、次のように呼び出します...
var myClass = new MyClass();
myClass.BeginDoSomething(
asyncResult => {
using (myClass)
{
myClass.EndDoSomething(asyncResult);
}
},
null);
しかし、どちらの方法でも、正しいタイミングでオブジェクトを破棄できるように、非同期操作が完了したことを呼び出し元に通知する方法が必要です。
非同期メソッドには通常、コールバックがあり、完了時に何らかのアクションを実行できます。これがあなたの場合、次のようなものになります:
// 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();
}
});
非同期処理を許可するためにコードを何らかの形で変更することはしません。代わりに、AsyncDoSomethingの呼び出しが行われたときに、実行に必要なすべてのデータのコピーを確実に取得します。そのメソッドは、リソースがある場合はすべてクリーンアップする必要があります。
コールバックメカニズムを追加し、クリーンアップ関数をコールバックとして渡すことができます。
var x = new MyClass();
Action cleanup = () => x.Dispose();
x.DoSomethingAsync(/*and then*/cleanup);
ただし、同じオブジェクトインスタンスから複数の非同期呼び出しを実行する場合、これは問題を引き起こします。
1つの方法は、カウントセマフォをセマフォクラスは、実行中の非同期ジョブの数をカウントします。
MyClassにカウンターを追加し、AsyncWhateverの呼び出しごとにカウンターを増やし、出口でそれを確認します。セマフォが0の場合、クラスは破棄する準備ができています。
var x = new MyClass();
x.DoSomethingAsync();
x.DoSomethingAsync2();
while (x.RunningJobsCount > 0)
Thread.CurrentThread.Sleep(500);
x.Dispose();
しかし、それが理想的な方法だとは思いません。デザインの問題のにおいがします。 MyClassデザインを再考することでこれを回避できるかもしれません。
MyClassの実装について少し教えてください。それが何をすべきか?
Microsoftは、オブジェクトの作成が継続を強制できる正気な方法がないため、実装がスレッドコンテキストからIDisposable
を呼び出すことをDispose
コントラクトの一部として要求していないことを残念に思います作成されたスレッドコンテキストの存在。オブジェクトを作成するスレッドが何らかの理由でオブジェクトが陳腐化するのを監視し、都合のよいときにControl.BeginInvoke
できるようにコードを設計することができます。オブジェクトはList<>
dですが、<=>を作成するスレッド側で特別な動作を必要としない標準メカニズムはないと思います。
おそらく最善の策は、関心のあるすべてのオブジェクトを共通のスレッド(おそらくUIスレッド)内で作成し、スレッドが関心のあるオブジェクトの存続期間中にとどまることを保証し、< =>オブジェクトの廃棄を要求します。オブジェクトの作成もクリーンアップもいずれの期間もブロックしない場合、それは良いアプローチかもしれませんが、いずれかの操作が別のアプローチをブロックできる場合は[おそらく、独自のスレッドで隠しダミーフォームを開くことができます。 <=> there]を使用します。
また、<=>実装を制御できる場合は、非同期で安全に起動できるように設計します。多くの場合、それは<!> quot; just work <!> quot;誰もアイテムが廃棄されたときに使用しようとしていないが、それはほとんど与えられていない。特に、<=>の多くのタイプでは、複数のオブジェクトインスタンスが共通の外部リソースを操作する可能性があるという現実的な危険があります(例:オブジェクトは、作成されたインスタンスの<=>を保持し、構築時にそのリストにインスタンスを追加し、<=>のインスタンスを削除できます。リスト操作が同期されていない場合、非同期に破棄されるオブジェクトが使用されていない場合でも、リストが破損する可能性があります。
ところで、有用なパターンは、オブジェクトが使用中に非同期的に破棄できるようにすることです。そのような破棄により、進行中の操作が最初の便利な機会に例外をスローすることが予想されます。ソケットのようなものはこのように機能します。ソケットを役に立たない状態のままにせずに読み取り操作を早期に終了することは不可能かもしれませんが、ソケットがとにかく使用されない場合、別のスレッドがそれを決定した場合、読み取りがデータを待機し続ける意味はありませんあきらめるはずです。私見、それがすべての<=>オブジェクトが振る舞うために努力すべき方法ですが、私はそのような一般的なパターンを要求する文書を知っていません。