私は方法があります。
private static void Method()
{
Console.WriteLine("Method() started");
for (var i = 0; i < 20; i++)
{
Console.WriteLine("Method() Counter = " + i);
Thread.Sleep(500);
}
Console.WriteLine("Method() finished");
}
そして私はこのタスクを新しいタスクで始めたいのです。私はこのような新しい仕事を始めることができます
var task = Task.Factory.StartNew(new Action(Method));
またはこれ
var task = Task.Run(new Action(Method));
しかし、Task.Run()
とTask.Factory.StartNew()
の間に違いはありますか。どちらもThreadPoolを使用し、Taskのインスタンスを作成した直後にMethod()を起動します。最初のバリアントを使用すべきときと2番目のときはいつですか。
2番目のメソッドTask.Run
は、.NET Frameworkの新しいバージョン(.NET 4.5)で導入されました。
しかし、最初のメソッドTask.Factory.StartNew
では、作成したいスレッドについて多くの便利なことを定義することができますが、Task.Run
ではこれを提供しません。
たとえば、長期実行タスクスレッドを作成したいとします。スレッドプールのスレッドがこのタスクに使用される予定の場合、これはスレッドプールの悪用と見なされる可能性があります。
これを回避するためにできることの1つは、別のスレッドでタスクを実行することです。このタスク専用の新しく作成されたスレッドは、タスクが完了すると破棄されます。YoucannotTask.Run
でこれを実現できますが、Task.Factory.StartNew
で以下のように実現できます。
Task.Factory.StartNew(..., TaskCreationOptions.LongRunning);
それが述べられているように ここで :
そこで、.NET Framework 4.5 Developer Previewでは、新しいTask.Runメソッドを紹介しました。 これはTask.Factory.StartNew、を廃止するものではなく、むしろTaskを使用する簡単な方法と考えるべきです。 Factory.StartNewで一連のパラメータを指定する必要はありません。これはショートカットです。実際、Task.Runは実際にはTask.Factory.StartNewに使用されているのと同じロジックの観点から実装されており、単にデフォルトのパラメータを渡すだけです。 Task.RunにActionを渡すと、
Task.Run(someAction);
これは次のものとまったく同じです。
Task.Factory.StartNew(someAction,
CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
違いについて説明している このブログ記事 を参照してください。基本的には
Task.Run(A)
することと同じです:
Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
Task.Run
は新しい.NETフレームワークのバージョンで導入されました、そしてそれは 推奨されます 。
.NET Framework 4.5以降、Task.Runメソッドが計算バインドタスクを起動するための推奨される方法です。 StartNewメソッドを使用するのは、長時間の計算処理が必要なタスクに対してきめ細かい制御が必要な場合だけにしてください。
Task.Factory.StartNew
にはもっと多くのオプションがあります、Task.Run
は省略形です:
Runメソッドは、デフォルト値を使用してタスクを簡単に開始できるようにする一連のオーバーロードを提供します。これはStartNewオーバーロードに対する軽量の代替手段です。
手短に言うと、技術的な ショートカット を意味します。
public static Task Run(Action action)
{
return Task.InternalStartNew(null, action, null, default(CancellationToken), TaskScheduler.Default,
TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, ref stackMark);
}
Stephen Clearyによるこの投稿によると、Task.Factory.StartNew()は危険です。
バックグラウンドスレッドで作業を進めるために、Task.Factory.StartNewを使用するブログやSO質問に多くのコードがあります。 Stephen Toubには、Task.RunがTask.Factory.StartNewより優れている理由を説明する優れたブログ記事がありますが、多くの人が読んでいない(または理解していない)と思います。それで、私は同じ議論を取り、もう少し強力な言葉を加えました、そしてこれがどのようになるかを見るでしょう。 :) StartNewにはTask.Run以外にもたくさんのオプションがありますが、これは非常に危険です。非同期コードでは、Task.Factory.StartNewよりもTask.Runを優先してください。
これが実際の理由です。
- 非同期デリゲートを理解しません。これは実際にはStartNewを使いたいという理由でポイント1と同じです。問題は、StartNewに非同期デリゲートを渡すときに、返されたタスクがそのデリゲートを表していると想定するのが自然なことです。ただし、StartNewは非同期デリゲートを理解しないため、そのタスクが実際に表すものはそのデリゲートの始まりにすぎません。これは、非同期コードでStartNewを使用するときにコーダーが遭遇する最初の落とし穴の1つです。
- わかりにくいデフォルトスケジューラ。わかりました、ちょっとした質問です。以下のコードで、メソッド「A」はどのスレッドで実行されますか。
Task.Factory.StartNew(A);
private static void A() { }
ええ、あなたはそれがトリックな質問であることを知っていますね。 「スレッドプールスレッド」と答えた場合は、すみませんが、それは正しくありません。 “ A”は、TaskSchedulerが現在実行しているものすべてに実行されます。
つまり、操作が完了した場合はUIスレッドで実行される可能性があり、Stephen Clearyが自身の投稿で詳しく説明しているように、継続によりUIスレッドにマーシャリングして戻る可能性があります。
私の場合は、ビジーなアニメーションも表示しながらビューのデータグリッドをロードするときにバックグラウンドでタスクを実行しようとしていました。 Task.Factory.StartNew()
を使用するとビジー状態のアニメーションは表示されませんでしたが、Task.Run()
に切り替えるとアニメーションは正しく表示されました。
詳しくは、 https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html をご覧ください。
すでに言及した人たち
Task.Run(A);
と同等です
Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
しかし、誰もそれについて言及しませんでした
Task.Factory.StartNew(A);
以下と同等です:
Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Current);
ご覧のとおり、Task.Run
とTask.Factory.StartNew
では2つのパラメーターが異なります。
TaskCreationOptions
-Task.Run
はTaskCreationOptions.DenyChildAttach
を使用します。これは、子タスクを親にアタッチできないことを意味します。これを考慮してください。
var parentTask = Task.Run(() =>
{
var childTask = new Task(() =>
{
Thread.Sleep(10000);
Console.WriteLine("Child task finished.");
}, TaskCreationOptions.AttachedToParent);
childTask.Start();
Console.WriteLine("Parent task finished.");
});
parentTask.Wait();
Console.WriteLine("Main thread finished.");
parentTask.Wait()
を呼び出すと、TaskCreationOptions.AttachedToParent
を指定したにもかかわらず、TaskCreationOptions.DenyChildAttach
が子供にアタッチすることを禁止しているため、childTask
は待機しません。 Task.Factory.StartNew
ではなくTask.Run
で同じコードを実行すると、Task.Factory.StartNew
はTaskCreationOptions.None
を使用するため、parentTask.Wait()
はchildTask
を待機します
TaskScheduler
-Task.Run
はTaskScheduler.Default
を使用します。つまり、デフォルトのタスクスケジューラ(スレッドプールでタスクを実行するスケジューラ)が常にタスクの実行に使用されます。一方でTask.Factory.StartNew
はTaskScheduler.Current
を使用します。これは現在のスレッドのスケジューラを意味し、TaskScheduler.Default
である場合がありますが、常にではありません。実際、Winforms
またはWPF
アプリケーションを開発する場合、現在のスレッドからUIを更新する必要があります。これを行うには、タスク内に別の長時間実行タスクを誤って作成する場合、TaskScheduler.FromCurrentSynchronizationContext()
タスクスケジューラを使用しますTaskScheduler.FromCurrentSynchronizationContext()
スケジューラを使用したUIはフリーズされます。これの詳細な説明は here にあります。
したがって、ネストされた子タスクを使用せず、常にスレッドプールでタスクを実行する場合は、より複雑なシナリオがない限り、Task.Run
を使用することをお勧めします。