1

非常に簡単なタスク:

Task.Factory.StartNew(() =>
{
    Thread.Sleep(5000);
}).ContinueWith(_ =>
{
    lblStatus.Text = "Done";
}, TaskScheduler.FromCurrentSynchronizationContext());

から実行すると期待どおりに実行されますForm_Load()が、BindingSourceに関連するイベントから実行すると5秒間ブロックされます。

BindingSourcesについて何かが足りませんか?.NET4を使用しています。

4

2 に答える 2

1

コードは 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 を更新します。

于 2012-06-27T14:12:05.827 に答える
0

デフォルトのタスクスケジューラをタスクに割り当てることでこれを解決しました。最終的なフォームは次のようになりました。

Task.Factory.StartNew(() =>
{
    Thread.Sleep(5000);
}, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).ContinueWith(_ =>
{
    lblStatus.Text = "Done";
}, TaskScheduler.FromCurrentSynchronizationContext());

私は C# の専門家ではないので、なぜこのように動作するのかよくわかりません。

于 2012-06-26T17:46:41.147 に答える