web-dev-qa-db-ja.com

Task.Run()とTask.Factory.StartNew()の違いは何ですか

私は方法があります。

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番目のときはいつですか。

135
Sergiy Lichenko

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);
142
Christos

違いについて説明している このブログ記事 を参照してください。基本的には

Task.Run(A)

することと同じです:

Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);   
25

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);
}
21
Zein Makki

Stephen Clearyによるこの投稿によると、Task.Factory.StartNew()は危険です。

バックグラウンドスレッドで作業を進めるために、Task.Factory.StartNewを使用するブログやSO質問に多くのコードがあります。 Stephen Toubには、Task.RunがTask.Factory.StartNewより優れている理由を説明する優れたブログ記事がありますが、多くの人が読んでいない(または理解していない)と思います。それで、私は同じ議論を取り、もう少し強力な言葉を加えました、そしてこれがどのようになるかを見るでしょう。 :) StartNewにはTask.Run以外にもたくさんのオプションがありますが、これは非常に危険です。非同期コードでは、Task.Factory.StartNewよりもTask.Runを優先してください。

これが実際の理由です。

  1. 非同期デリゲートを理解しません。これは実際にはStartNewを使いたいという理由でポイント1と同じです。問題は、StartNewに非同期デリゲートを渡すときに、返されたタスクがそのデリゲートを表していると想定するのが自然なことです。ただし、StartNewは非同期デリゲートを理解しないため、そのタスクが実際に表すものはそのデリゲートの始まりにすぎません。これは、非同期コードでStartNewを使用するときにコーダーが遭遇する最初の落とし穴の1つです。
  2. わかりにくいデフォルトスケジューラ。わかりました、ちょっとした質問です。以下のコードで、メソッド「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 をご覧ください。

18
user8128167

すでに言及した人たち

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.RunTask.Factory.StartNewでは2つのパラメーターが異なります。

  1. TaskCreationOptions-Task.RunTaskCreationOptions.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.StartNewTaskCreationOptions.Noneを使用するため、parentTask.Wait()childTaskを待機します

  2. TaskScheduler-Task.RunTaskScheduler.Defaultを使用します。つまり、デフォルトのタスクスケジューラ(スレッドプールでタスクを実行するスケジューラ)が常にタスクの実行に使用されます。一方でTask.Factory.StartNewTaskScheduler.Currentを使用します。これは現在のスレッドのスケジューラを意味し、TaskScheduler.Defaultである場合がありますが、常にではありません。実際、WinformsまたはWPFアプリケーションを開発する場合、現在のスレッドからUIを更新する必要があります。これを行うには、タスク内に別の長時間実行タスクを誤って作成する場合、TaskScheduler.FromCurrentSynchronizationContext()タスクスケジューラを使用しますTaskScheduler.FromCurrentSynchronizationContext()スケジューラを使用したUIはフリーズされます。これの詳細な説明は here にあります。

したがって、ネストされた子タスクを使用せず、常にスレッドプールでタスクを実行する場合は、より複雑なシナリオがない限り、Task.Runを使用することをお勧めします。

10