私は通常、フォームに次のようなコードがあります。
private void PerformLongRunningOperation()
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate
{
// perform long running operation here
};
worker.RunWorkerAsync();
}
これは、BackgroundWorker
を破棄しないことを意味しますが、フォームデザイナによって追加した場合は、破棄されると思います。
これは問題を引き起こしますか?モジュールレベルの_saveWorker
を宣言してから、フォームのdisposeメソッドからDispose
を呼び出す方が正しいですか?
はい、バックグラウンドワーカーを処分する必要があります。
後でクリーンアップする必要のないThreadPool.QueueUserWorkItem(...)
を使用する方が簡単な場合があります。
Dispose()を常に呼び出す必要がある理由の詳細:
BackgroundWorkerクラスを見ると、disposeメソッドで実際にはスレッドのクリーンアップは行われませんが、クラスがガベージコレクターに与える影響のため、Disposeを呼び出すことは依然として重要です。
ファイナライザーのあるクラスはすぐにはGCされません。それらは保持され、ファイナライザーキューに追加されます。次に、ファイナライザースレッドが実行されます(標準パターンの呼び出しに従ってdisposeが実行されます)。これは、オブジェクトがGC第1世代まで存続することを意味します。また、第1世代のコレクションは第0世代のコレクションよりもはるかにまれであるため、オブジェクトはメモリ内にずっと長く留まります。
ただし、Dispose()を呼び出すと、オブジェクトはファイナライズキューに追加されないため、ガベージコレクションを自由に行うことができます。
それほど大きな問題ではありませんが、大量に作成すると、必要以上のメモリを使用することになります。 disposeメソッドを持つオブジェクトに対して常にdisposeを呼び出すことは、実際には良い習慣と見なされるべきです。
ですから、全体として、100%ハードで高速な要件ではないと思います。 Dispose()を呼び出さないと、アプリが爆発したり、メモリがリークしたりすることはありませんが、状況によっては悪影響が生じる可能性があります。バックグラウンドワーカーは、WinFormsコンポーネントとして使用するように設計されているため、そのように使用します。要件が異なり、WinFormsコンポーネントとして使用したくない場合は、使用せず、ジョブに適したツールを使用してください。 ThreadPoolのように。
課題は、実行が終了したBackgroundWorker
afterのみを破棄することを確認することです。 Completed
イベントでは、BackgroundWorker自体によって発生するため、これを行うことはできません。
BackgroundWorkerは、実際にはWinFormsフォームのコンポーネントとして使用することを目的としているため、これを行うか、Thread.QueueUserWorkItem
のようなものに切り替えることをお勧めします。これはスレッドプールスレッドを使用し、終了時に特別なクリーンアップを必要としません。
私の見解では、一般に、IDisposableの場合は、使い終わったらDispose()dにする必要があります。 BackgroundWorkerの現在の実装を技術的に破棄する必要がない場合でも、後の内部実装に驚かされることはありません。
気にしないでください。Bgwが保持できる唯一のリソースはスレッドです。デリゲートに無限ループがない場合は問題ありません。
BackgroundWorkerはComponentからIDisposable()
を継承しますが、実際には必要ありません。
メソッドをThreadPoolに直接プッシュするのと比較してください。スレッドを破棄しません(できません)。プールからのスレッドではありません。
ただし、CompletedイベントまたはProgress/Cancel機能を使用していないという意味でサンプルが完了している場合は、ThreadPool.QueueUserWorkItem()
を使用することもできます。
RunWorkerCompleted
イベントでdisposeを呼び出します。
BackgroundWorker wkr = new BackgroundWorker();
wkr.DoWork += (s, e) => {
// Do long running task.
};
wkr.RunWorkerCompleted += (s, e) => {
try {
if (e.Error != null) {
// Handle failure.
}
} finally {
// Use wkr outer instead of casting.
wkr.Dispose();
}
};
wkr.RunWorkerAsync();
追加の試行/最後は、完了コードで例外が発生した場合にDispose
が呼び出されるようにすることです。
Usingステートメントでラップしてみませんか?それほど余分な労力はなく、あなたは処分を受けます:
private void PerformLongRunningOperation()
{
using (BackgroundWorker worker = new BackgroundWorker())
{
worker.DoWork += delegate
{
// perform long running operation here
};
worker.RunWorkerAsync();
}
}
編集:
さて、私はちょっとしたテストをまとめて、処分で何が起こっているのか、何が起こっていないのかを確認しました。
using System;
using System.ComponentModel;
using System.Threading;
namespace BackgroundWorkerTest
{
internal class Program
{
private static BackgroundWorker _privateWorker;
private static void Main()
{
PrintThread("Main");
_privateWorker = new BackgroundWorker();
_privateWorker.DoWork += WorkerDoWork;
_privateWorker.RunWorkerCompleted += WorkerRunWorkerCompleted;
_privateWorker.Disposed += WorkerDisposed;
_privateWorker.RunWorkerAsync();
_privateWorker.Dispose();
_privateWorker = null;
using (var BW = new BackgroundWorker())
{
BW.DoWork += delegate
{
Thread.Sleep(2000);
PrintThread("Using Worker Working");
};
BW.Disposed += delegate { PrintThread("Using Worker Disposed"); };
BW.RunWorkerCompleted += delegate { PrintThread("Using Worker Completed"); };
BW.RunWorkerAsync();
}
Console.ReadLine();
}
private static void WorkerDisposed(object sender, EventArgs e)
{
PrintThread("Private Worker Disposed");
}
private static void WorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
PrintThread("Private Worker Completed");
}
private static void WorkerDoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(2000);
PrintThread("Private Worker Working");
}
private static void PrintThread(string caller)
{
Console.WriteLine("{0} Thread: {1}", caller, Thread.CurrentThread.ManagedThreadId);
}
}
}
出力は次のとおりです。
Main Thread: 1
Private Worker Disposed Thread: 1
Using Worker Disposed Thread: 1
Private Worker Working Thread: 3
Using Worker Working Thread: 4
Using Worker Completed Thread: 4
Private Worker Completed Thread: 3
いくつかのテストから、Dispose()は基本的に開始されたBackgroundWorkerに影響を与えないようです。 usingステートメントのスコープ内で呼び出すか、コードで宣言されたものを使用してすぐに破棄して逆参照するかに関係なく、正常に実行されます。 Disposedイベントはメインスレッドで発生し、DoWorkとRunWorkerCompletedはスレッドプールスレッド(イベントが発生したときに使用可能な方)で発生します。 Disposeを呼び出した直後(つまり、DoWorkが完了する前に)にRunWorkerCompletedイベントの登録を解除し、RunWorkerCompletedが起動しなかったケースを試しました。これにより、BackgroundWorkerオブジェクトは破棄されても操作できると思います。
したがって、他の人が言及しているように、現時点では、Disposeを呼び出す必要はないように見えます。しかし、少なくとも私の経験とこれらのテストからは、それを行うことにも害は見られません。
すべてのIDisposableオブジェクトに対してDispose()を呼び出すことがベストプラクティスと見なされます。これにより、ハンドルなど、保持している可能性のある管理されていないリソースを解放できます。 IDisposableクラスにはファイナライザーも必要です。ファイナライザーが存在すると、GCがそれらのオブジェクトを完全に収集できる時間を遅らせることができます。
クラスがIDisposableを割り当て、それをメンバー変数に割り当てる場合、一般に、クラス自体もIDisposableである必要があります。
ただし、Dispose()を呼び出さない場合、ファイナライザーが最終的に呼び出され、リソースが最終的にクリーンアップされます。明示的に呼び出すことの利点は、これらのことがより迅速に、より少ないオーバーヘッドで発生する可能性があることです。これにより、アプリのパフォーマンスが向上し、メモリの負荷が軽減されます。
完了ハンドラーは元のスレッドで実行されます(つまり、スレッドプールのバックグラウンドスレッドではありません)。あなたのテスト結果は実際にその前提を確認します。