コードは ThreadPool スレッドを 5 秒間ブロックします。多数のイベントに対してこのコードを実行すると、すべてのスレッドプール スレッドが使い果たされ、すべての Sleep ステートメントが終了するまでアプリケーションが効果的にブロックされる可能性があります。
どちらのコード サンプルも、既定のスレッド スケジューラを使用して実行されます。違いは、2 番目のサンプルが TaskCreationOptions.LongRunning を使用して、スケジューラにプール スレッドを待機するのではなく、新しいスレッドを作成するように指示することです。これは最初の問題を解決するかもしれませんが、まだスレッドを浪費しており、他のタスクに使用できるスレッドを残さない可能性があるため、適切な解決策ではありません。
正しい実装は、タイマーが切れたときに通知される TaskSource を使用することです。この方法では、スレッドをブロックしていません。
C# 5 では、Task.Delayメソッドで既にこれをサポートしています。Visual Studio 2012の非同期ターゲティング パックまたは2010のAsync v3 CTPを使用する場合、これを .NET 4.0 で使用できます。
ParallelExtensionExtras ライブラリにも同様のメソッドがあります。TaskFactory.StartNewDelayed拡張メソッドは、ほぼ同じように機能します。Stephen Toub の記事のサンプル コードは、簡略化されたバージョンを提供します。
public static Task StartNewDelayed(int millisecondsDelay, Action action)
{
// Validate arguments
if (millisecondsDelay < 0)
throw new ArgumentOutOfRangeException("millisecondsDelay");
if (action == null) throw new ArgumentNullException("action");
// Create a trigger used to start the task
var tcs = new TaskCompletionSource<object>();
// Start a timer that will trigger it
var timer = new Timer(
_ => tcs.SetResult(null), null, millisecondsDelay, Timeout.Infinite);
// Create and return a task that will be scheduled when the trigger fires.
return tcs.Task.ContinueWith(_ =>
{
timer.Dispose();
action();
});
}
ParallelExtensionsExtras のバージョンを使用すると、コードを次のように書き直すことができます。
Task.Factory.StartNewDelayed(5000).ContinueWith(_ =>
{
lblStatus.Text = "Done";
}, TaskScheduler.FromCurrentSynchronizationContext());
編集:
結局、実際のコードには Thread.Sleep がないようです。これは、いくつかの重い DB 関連の操作を実行します。効果は同じですが。各 BindingSource イベントの後に新しいタスクを開始すると、非常に多くのタスクが実行され、スレッドプールが使い果たされる可能性があります。
1 つの解決策は、 TaskFactory.StartNew(Action,TaskCreationOptions)オーバーライドでTaskCreationOptions.LongRunning フラグを使用して、スケジューラにさらにスレッドを作成するように指示することです。
さらに優れたソリューションは、BeginExecuteXXX、EndExecuteXXX メソッドをTaskFactory.FromAsyncと組み合わせて使用し、非同期呼び出しをタスクに変換して、データベース操作を非同期で実行することです。このようにして、データベース操作はスレッドをまったくブロックしません。
次のように書くことができます。
Task<SqlDataReader> task = Task<SqlDataReader>.Factory.FromAsync(
cmd.BeginExecuteReader(CommandBehavior.CloseConnection),
cmd.EndExecuteReader)
.ContinueWith(reader=>
{
//do some processing
reader.Close();
});
.ContinueWith(_ =>
{
lblStatus.Text="Done";
},TaskScheduler.FromCurrentSynchronizationContext());
データを非同期に読み取って処理し、処理が終了したら UI を更新します。