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


189

私には方法があります:

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を使用しており、タスクのインスタンスを作成した直後にMethod()を開始します。最初のバリアントをいつ使用し、いつ2番目に使用するか?


6
実際、StartNewはThreadPoolを使用する必要はありません。私の回答でリンクしているブログを参照してください。問題はStartNewデフォルトで使用され、TaskScheduler.Currentこれはスレッドプールである可能性がありますが、UIスレッドである可能性もあります。
Scott Chamberlain

回答:


194

2番目の方法はTask.Run、(。NET 4.5の).NETフレームワークの新しいバージョンで導入されました。

ただし、最初の方法はTask.Factory.StartNew、作成したいスレッドに関する多くの便利なものを定義する機会をTask.Run提供しますが、これを提供しません。

たとえば、実行時間の長いタスクスレッドを作成するとします。スレッドプールのスレッドがこのタスクに使用される場合、これはスレッドプールの乱用と見なすことができます。

これを回避するためにできることの1つは、タスクを別のスレッドで実行することです。このタスク専用で、タスクが完了すると破棄される、新しく作成されたスレッドこれをで達成することはできませんTask.RunTask.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に渡すと、次のようになります。

Task.Run(someAction);

これは次とまったく同じです。

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

4
ステートメントthat’s exactly equivalent toが保持されていないコードがあります。
Emaborsa 2017

7
@Emaborsaこのコードを投稿して、議論を詳しく説明していただければ幸いです。前もって感謝します !
クリストス2017

4
あなたは要点、作成することができ@Emaborsa gist.github.comをし、それを共有しています。ただし、この要点を共有することを除いて、そのフレーズtha's exactly equivalent toが保持しない結果にどのように到達したかを指定してください。前もって感謝します。コードについてコメントで説明するとよいでしょう。ありがとう:)
クリストス

8
また、Task.Runはデフォルトでネストされたタスクのラップを解除することにも言及する価値があります。:私は、主要な違いについては、この記事で読むことをお勧めしますblogs.msdn.microsoft.com/pfxteam/2011/10/24/...
パヴェル・マガ

1
@ The0bserverいいえ、そうですTaskScheduler.Defaultreferencesource.microsoft.com/#mscorlib/system/threading/Tasks/…をご覧ください。
クリストス

45

人々はすでにそれを述べました

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およびの2つのパラメーターは異なりますTask.Factory.StartNew

  1. 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()childTask指定したにもかかわらず、待機しませんTaskCreationOptions.AttachedToParentれ。これは、TaskCreationOptions.DenyChildAttach子がそれにアタッチすることを禁止しているためです。あなたと同じコードを実行した場合Task.Factory.StartNewの代わりにTask.RunparentTask.Wait()待ちますchildTaskので、Task.Factory.StartNew用途TaskCreationOptions.None

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

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


1
これは素晴らしいヒントであり、受け入れられる答えになるはずです
Ali Bayat


28

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

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

ブログやSOの質問に、Task.Factory.StartNewを使用してバックグラウンドスレッドで作業を開始するコードがたくさんあります。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で実行されます。

つまり、Stephen Clearyが彼の投稿でより完全に説明しているように、操作が完了し、継続のためにUIスレッドにマーシャリングすると、UIスレッドで実行される可能性があります。

私の場合、ビジーなアニメーションを表示しながらビューのデータグリッドをロードするときに、バックグラウンドでタスクを実行しようとしました。使用中はビジーなアニメーションが表示されませんでしたTask.Factory.StartNew()が、切り替えたときにアニメーションが正しく表示されましたTask.Run()

詳細については、https://blog.stephencleary.com/2013/08/startnew-is-dangerous.htmlを参照してください


1

類似性、つまりTask.Run()がTask.Factory.StartNew()の省略形であることは別として、同期デリゲートと非同期デリゲートの場合の動作には微妙な違いがあります。

次の2つの方法があるとします。

public async Task<int> GetIntAsync()
{
    return Task.FromResult(1);
}

public int GetInt()
{
    return 1;
}

次のコードを考えてみましょう。

var sync1 = Task.Run(() => GetInt());
var sync2 = Task.Factory.StartNew(() => GetInt());

ここでは、sync1とsync2の両方がTask <int>タイプです。

ただし、非同期メソッドの場合は異なります。

var async1 = Task.Run(() => GetIntAsync());
var async2 = Task.Factory.StartNew(() => GetIntAsync());

このシナリオでは、async1のタイプはTask <int>ですが、async2のタイプはTask <Task <int>>です。


はい、メソッドのTask.Runアンラップ機能が組み込まれているからUnwrapです。ここでは、この決定の背後にある理由を説明したブログ記事です。
Theodor Zoulias

-8

2つのサービスを呼び出すアプリケーションで、Task.RunとTask.Factory.StartNewの両方を比較しました。私の場合、どちらも問題なく動作することがわかりました。ただし、2番目の方が高速です。


正解ではないかもしれませんが、なぜこの回答が「10」の反対投票に値するのかわかりません...
Mayer Spitzer
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.