Task.RunとTask.Factory.StartNewの比較表
項目 | Task .Run | Task .Factory .StartNew | 戻り値 | 備考 | |
---|---|---|---|---|---|
指定可能な引数 | CancellationToken | ○ | ○ | - | キャンセルに使うTokenを指定 |
TaskCreationOptions | × | ○ | - | どのように作成するかを指定するオプション | |
TaskScheduler | × | ○ | - | 同期コンテキストを 指定 |
|
第1引数 | Action | ○ | ○ | Task | |
Func <TResult> | ○ | ○ | Task <TReuslt> | ||
Action <object> | × | ○ | Task | 第2引数に Ojbectが必須 |
|
Func <Object,TResult> | × | ○ | Task <TReuslt> |
||
Func <Task> | ○ | × | Task | 入れ子のTaskの 内側が戻る |
|
Func <Task<TResult>> | ○ | × | Task <TReuslt> |
使い勝手がいいTask.Run
比較表のとおり、Task.RunはTask.Factory.StartNewに比べ、設定できる項目が少ない。というか、Taskを作るデリゲートの他には、CancellationTokenしかない。
MSDNのブログで次の説明があった。
Task.Run(someAction);that’s exactly equivalent to:
Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);Task.Run vs Task.Factory.StartNew | Parallel Programming with .NET
つまり、Task.RunはTask.Factory.StartNewで次のように設定したときと同じ。
設定項目 | 値 | 説明 |
---|---|---|
CancellationToken | None | CancellationTokenを渡さない(この項目はTask.Runでも変更可能) |
TaskCreationOptions | DenyChildAttach | 子タスクを親タスクにアタッチさせない |
TaskScheduler | Default | タスクスケジューラのデフォルト(スレッドプールで実行) |
普通の用途では問題のない設定になっている。この設定を変えたい場合はTask.Factory.StartNewを使う必要があるけど、自分の場合、そういう状況がめったにない。
上記ブログによると、Task.Facotry.StartNewを一般的な用途でもっと手早く使うためにTask.Runが作られたようだ。確かに、タイプ数が少ないし使い勝手が良い。自分も多用している。
ただ、Task.RunのTaskCreationOptionが「DenyChildAttach」なので、Task.Runの中でTask.Factory.StartNewを「AttachedToParent」で使っても無効になってしまうことには注意が必要。
また、Task.Runは手軽だけど細かい設定はできない。Task.Factory.StartNewが不要になったわけではない。
非同期メソッドを想定したTask.Runのオーバーロード
次に、それぞれに渡す第1引数について見る。第1引数にはタスクで行う処理の中味となるデリゲートを渡すことになっている。
Task.Run、Task.Factory.StartNewともに「Action」と「Func<TResult>」というデリゲートの基本の型を引数にとることができる。
基本形に加え、Task.Factory.StartNewの方では引数を一つだけ取るデリゲート「Action<object>」「Func<object, TResult>」が用意されている。(引数が2つ以上のを使いたかったら?たぶん「ラムダ式でくるんで、入れられる型にしてね(はぁと」ということなのだと思う。でも、それならそもそもActionとFunc<TResult>だけでいいいんじゃ…とも思うし、よくわからない……。)
Task.Runの方は、「Func<Task>」と「Func<Task<TResult>>」が加わっている。意図は明白で(というか、さっきのブログに書いてたから知ったんだけど)非同期メソッドへの対応だ。
非同期メソッドの戻り値は前に書いた通り「void」「Task」「Task<T>」の3種。voidはActionで対応できるし、残り二つの戻り値に対応したFuncが追加されているわけだ。(Funcに引数がある場合はどうしたら……ってやっぱり「ラムダ式で(ry」なのだと思う。)
『非同期メソッドに対応ったって、Func<Task>もFunc<Task<TResult>>もFunc<TResult>で受けることもできるんだから、意味ないじゃん』と思うなかれ。比較表の戻り値をご覧じろ。
非同期メソッドを入れてTaskを作ると「入れ子のTask」になる。非同期メソッドから返されるTaskが、コンストラクタの作るTaskにくるまれるからだ。
TaskFactory.StartNewの場合、自分が作った入れ子のTaskをそのまま返してくる。が、Task.Newは自分で作ったTaskの包装は取って(UnWrap)非同期タスクから返されたTaskを渡してくれる。
たとえば、Task<int>を返す非同期メソッドをTaskFactory.StartNewに与える場合の戻り値は
Task<Task<int>> task = Task.Factory.StartNew(asyncTaskInt);
Task<T>ではな<Task<Task<T>>となる。(まぁ、これが普通だ。)
が、Task.Runの場合
Task<int> task = Task.Run(asyncTaskInt);
Task<int>でいい。
便利だ!!でもこれ、知らないとハマることがある。だって、普通にTaskをnewしたときと型が変わっちゃうということだから…(実体験)。
まとめ
- Task.Runは手軽で便利だ!
- Task.Factory.StartNewを一般的な設定で使うのと同じ。
- Taks.Runは戻り値も便利だ!
- Task<TResult>の包みをほどいてTResultを渡してくれる。