C#でスレッドを使う場合「Threadはもう古い、時代はTaskや!」みたいなサイトをよく見かけますが、単純に処理を行う1スレッドを起こす場合もTaskの方が優れているのですか?
例えばメインスレッドはUIを処理するとして、起動中ずっとバックグラウンドで処理を行いたい場合など。
Treadは起動時の作成時のコストが重い等書かれていますが、1度起動してそのままずっと居座って処理を続けるような場合においてもTaskが用意しているスレッドプールの機構よりも重いんでしょうか?
Taskを盲信しているように見えてしまいます。あとあまり触れられないBackgroundWorkerは…
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答7件
0
Taskの目的は性能向上ではなく利便性の向上です。
ThreadPoolがあれば改善するのは速度ではなく、「スレッドの上限を超えてしまう問題」や「スレッド数を制限する」とかそうした煩雑さとの決別にあります。
非同期プロシージャコールを考えるより、Taskとして積み上げてしまった方が簡単です。
async-awaitプログラミングには必要不可欠だったし、わずか10行のコードを書いてサブスレッドで実行させるなんてのに毎回難しいことはしたくない。
IOバウンドな非同期タスクの実装の簡易化はThreadからの進化の最良の例といえるでしょう。
Threadインスタンスを直接保持しないというのも利点です。
Taskの利便性向上が目的ということの証左に、Taskは例外も拿捕しやすくなっています。
Thread内で起きる例外をメインアプリケーション側でフックするのは比較的難しい行為です。
しかし、TaskはAggregateExceptionで呼び元に返してくれることや、UnhandledExceptionもAppDomainよりも内側で返してくれます。
Taskの進化はThreadで実装しようと思った時に高度に要求された内容を咀嚼したものです。
C#は最新技術を搭載しようぜ!でも使う方はよくわかんなくてもいいようにしたいね、みたいな風変りな圧力が掛かった言語だと思います。
そういったフランクさがC#の良いところだと思います。
殆どの言語が新しい概念をデフォルトで使えるように進化しているので、それは時流といえば時流ですし、
古いコードはどんどんobsoleteになってしまったり新しい概念と衝突したりといった問題も抱えているので、良いところばかりとは言えないんでしょうけどね。
TaskはThreadをより高水準化した再実装なのです。
よりネイティブな方が早そうじゃん、って言われても、測って、とか、突き詰めればそれはそうでしょうね、としか言えません。
正に、C++とC#の違いですよ。Threadと、Taskっていうのは。
#追記
再実装という言い回しには誤りがあったので訂正します。
ソースを提示するのが一番適切な回答であると判断に至り、追記させて頂きます。
Task自身は実行に関するロジックを持っておらず、該当箇所はThreadPoolTaskScheduler内にあります。
Taskの実行にあたりTaskScheduler.Currentが呼び出されます。
ThreadPoolTaskSchedulerはTaskSchedulerクラスのデフォルト実装です。
https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/ThreadPoolTaskScheduler.cs
最終的にはLongRunningのオプションの有無によってThreadか、ThreadPoolにTaskを振り分ける実装であり、動作速度自体はラップの有無程度になると思います。
実行ソースはシンプルなので、ThreadとTaskの距離感も分かりやすいのではないでしょうか。
C#
1 protected internal override void QueueTask(Task task) 2 { 3 if ((task.Options & TaskCreationOptions.LongRunning) != 0) 4 { 5 // Run LongRunning tasks on their own dedicated thread. 6 Thread thread = new Thread(s_longRunningThreadWork); 7 thread.IsBackground = true; // Keep this thread from blocking process shutdown 8 thread.Start(task); 9 } 10 else 11 { 12 // Normal handling for non-LongRunning tasks. 13 bool forceToGlobalQueue = ((task.Options & TaskCreationOptions.PreferFairness) != 0); 14 ThreadPool.UnsafeQueueCustomWorkItem(task, forceToGlobalQueue); 15 } 16 }
Threadクラス自体の細かい情報が必要で、自身による拡張の特殊性がMS製のTaskを上回るならThreadを使う判断は有りだ、と考えてよいと思います。
投稿2017/07/05 04:25
編集2017/07/11 04:53総合スコア1591
0
少数の固定的な仕事を行うThreadを完全に制御したいならThreadでかまわないと思うのですが、単にそのようなスレッド制御を書くより、その時々で並列に処理したい小さな処理断片をスレッドプールへ「よきにはからっておいてください」というスタイルの方が簡単なことが多いというだけの話であると思います。
真っ当なThreadを使うとすると「どのアプリケーションでもにたような制御を毎回書くのがめんどくさい」からこそTaskを使うということだと思います。
「その時々で並列に処理したい小さな処理断片を一々Threadを起こして実行する」といったことに比べれば明らかにTask(スレッドプール)を使うことの方が優れていると思います。
しかしライフサイクルが長く、並列に行いたい処理がスレッド毎に明確に設計されているならThreadで一向にかまわないと思います。例がよくないですが、例えばガベージコレクションを一定時間ごとに自律的に行ってくれるスレッド・・・といったものですとThreadプールに細切れで処理を投げるのではなく専用のThreadを起動する方が適切ではないでしょうか?
投稿2017/07/05 02:06
編集2017/07/05 03:00総合スコア18394
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
こんにちは。
Taskはコンソール・アプリだとawaitの前後でスレッドが切り替わるので落とし穴に嵌りそうです。awaitの前後でThreadLocalのインスタンスが異なるとか、なかなか恐怖です。
TaskはGUIとの連携を素直に記述できるので優れていると思いますが、非GUI環境ではあまり意味がないし、デメリットもある仕組みです。
デメリットを考慮せず、盲目的に特定の仕組みを推奨する人は「アマチュア」なので、話半分に聞いておいた方がよいかも知れません。
投稿2017/07/05 02:29
総合スコア23272
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/07/05 02:52 編集
2017/07/05 02:56
2017/07/05 03:23
2017/07/05 05:57
2017/07/05 06:22
0
性能の話に絞って、用途別にどのクラスを使うべきか、私の認識を整理するとこんな感じになります。
■10秒以上の長い処理
性能的にはどのクラスを使っても同じなので好きなのを使って良い。一応、この中では最もシンプルでオーバーヘッドが少ない Thread を使うのが最適。Task を使う場合は LongRunning オプションを付ければ Thread を使ったときと同じになる(ThreadPool を使わなくなる)ので好みで付ける。
■レイテンシ重視のタスク処理、非同期処理
Task 固有の機能(終了待機、例外処理)が必要なときは Task、そうでない場合は ThreadPool が最適。Task を使うときは必ず PreferFairness オプションを付ける。付けないと、高負荷時にワークスティーリングの影響でレイテンシが超不安定になる。例えば平均1msで終わるタスクで、時々1000msかかるタスクが出てくるとかも起こり得る。Graniさんの、これが原因で不安定だったのかも。
■スループット重視のタスク処理
一応 Task が最適。ワークスティーリングの効果で ThreadPool よりタスク管理のオーバーヘッドが少ない。ただ、経験上、ThreadPool との差はほとんど分からないレベル。ワークスティーリング機能の特徴でタスクの実行順がぐちゃぐちゃになるので、扱うデータの種類によっては、CPUキャッシュが効果的に働かず性能が出ない場合もある。その場合は PreferFairness オプションを付ける。PreferFairness オプションを付けると実質 ThreadPool と同じになるので、Task 固有の機能が不要なら ThreadPool を使った方が良い。
■GUIで5秒以上の長い処理を実行
単純な処理のときは BackgroundWorker が最適。複雑な処理のときは ThreadPool と Dispatcher を組み合わせて使うのが最適。BackgroundWorker の実行は ThreadPool で行われるので性能は ThreadPool と同じ。GUI以外での利用は想定していないと思われる。
■極限まで性能を出す必要のあるタスク処理
CPU論理スレッド分のスレッドを立てて、スレッド関係を固定し、NUMA構成とキャッシュを考慮し、自力でスケジューラーを書く。SQL Server がこの方式。Graniさんのもこの方式。この場合、制御方法に特徴があるから性能が出るのであって、Thread を使ったから性能が出ると言うわけではない。他のクラスを使っても同じ性能が出る。
■ThreadPool と Task の動作の違い
ThreadPool は単一のキューでタスクを管理するシンプルな方式。Task はデフォルトではワークスティーリングと言って単一のグローバルキューと複数のローカルスタック(ワーカースレッド毎にある)でタスクを管理する方式。キューよりスタックの方がオーバーヘッドが少なく、キャッシュが効きやすい。また、スタックがワーカースレッド毎に存在するため、スレッド間同期のオーバーヘッドが少なくて済む。頻繁に追加タスクが発生する場合やコア数が多いCPUを使っている場合は性能的に有利になる。
Task のワークスティーリングの説明
https://blogs.msdn.microsoft.com/jennifer/2009/06/26/work-stealing-in-net-4-0/
Local Queue と書いてあるが実際の動作はスタック。
Taskは終了待機(スレッド間同期)の機能がある分、オーバーヘッドが多い。ただ、ThreadPool を使っても結局自分でスレッド間同期のコードを書く必要があるので、全体としては変わらない。
■スループットとレイテンシ
スループットは単位時間にこなせるタスクの量(数)。レイテンシはタスクを依頼して結果が返ってくるまでの時間。ThreadPool がスループットとレイテンシのバランスが取れているのに対し、Task のデフォルト(ワークスティーリング有効)はスループット特化型でレイテンシは平均的に長く、不安定。
■サービスの実装
haru666さんも書かれていますが、一般的なサービス、特に不特定多数から同時にリクエストを受けるネットワークサービスを書く場合は、スタートアップコードで非同期処理を開始して、以降スレッドプールで処理するのが性能的には一番良い方法になります。この場合、メインスレッド的な固定スレッドは必要ありません。サービス提供先が限られた相手しかいないときは、メインスレッド的な固定スレッドを最初に起動して処理した方が実装が簡単で良いと思います。
■結論
性能的に見て、新しいから必ず Task が良いと言うわけではなく、用途に応じて使い分けをした方が良いです。特に Task のデフォルト動作、ワークスティーリングの動作には注意が必要です。私は Task はあまり使わず、ThreadPool で書くことが多いです。Task は機能的に細かい制御がやりづらいし、性能的にも不安定で扱いにくい感じがしますので。
投稿2017/07/11 15:13
編集2017/07/11 23:03退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
あまり詳しくない自分がレスするのも何ですが・・・
質問者さんの言われる Thread とは、「Threadはもう古い、時代はTaskや!」という言葉から想像して、Task とか async / await がなかった時代の Thread のこと、つまり以下の記事で言う Thread のことですよね。
.NETにおけるマルチスレッド・プログラミングの方法
http://www.atmarkit.co.jp/ait/articles/0504/20/news111.html
その記事に書いてありますが "サーバ型のプログラムのように、リクエストを並行して処理するようなプログラムの場合、スレッドの生成・破棄を大量に繰り返す必要が出てくる。スレッドの生成にはそれなりのリソースが消費されるため、単純にこれを繰り返していてはパフォーマンスが低下する" というデメリットがあるそうです。
上に紹介した記事に書いてありますが "この問題を解決する仕組みが「スレッドプール」である" ということだそうです。
そして、Task は以下の記事に書いてありますように ".NET Framework 4 では、スレッド プールをより使いやすくするために、Task(System.Threading.Tasks 名前空間)というクラスが導入されました" ということで、基本的には ThreadPool だそうです。
[雑記] スレッド プールとタスク
http://ufcpp.net/study/csharp/misc_task.html
ということで、Thread と Task を使うのは最初の記事のメリット / デメリットを天秤にかけて決める話ではないかと思います・・・が、MSDN Blog の記事(URL 下記)によると、Thread を使うのは、その記事を書いた Microsoft の開発者よりはるかにスマートに実装できるのでなければ "don't even think about it" だそうです。
Performing Asynchronous Work, or Tasks, in ASP.NET Applications
https://blogs.msdn.microsoft.com/tmarq/2010/04/14/performing-asynchronous-work-or-tasks-in-asp-net-applications/
理由はその記事の FAQ の 4 番目に書いてありますが、以下に概略を書いておきます(誤訳はあるかも)。
(1) CLR ThreadPool を使用するのに比べて非常にコストが高い。
(2) 自分で作った Thread に I/O 要求が残ってないか終了前にチェックしなければならない。
(3) システムのパフォーマンスを保つには実行されている Thread の数が適切でなければならないが、自分で Thread を作るのであればパフォーマンスを保つのは自分の責任になる。
そう言われては、少なくとも自分的には Thread を使うという選択肢なないかなと思ってしまいます。
ご参考まで。
投稿2017/07/05 03:21
編集2017/07/05 03:23退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/07/05 07:20
退会済みユーザー
2017/07/05 07:48
2017/12/26 03:42 編集
0
既定では ThreadPool 相当ですが、TaskCreationOptions.LongRunning オプションもあります。
そのため、従来の ThreadPool と Thread を同じように簡単に使えるものとして整頓したものと考えて良いかと思います。
ぱっと見、LongRunning時はThreadを利用しているようですので、動作後の差はなさそうです。
BackgroundWorkerは… 動作的に怪しかったので使ってません。
投稿2017/07/05 03:35
編集2017/07/05 11:07総合スコア385
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
最近でも、こういうthread事例もある。 リアルタイム通信におけるC# - async-awaitによるサーバーサイドゲームループ
Taskは小さい処理をたくさん使うのには向いているとはよくいう。
BackgroundWorkerは、お亡くなりになりました。UWPには、入っていない。
投稿2017/07/05 02:22
総合スコア1984
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/07/05 06:10
2017/07/05 06:42
2017/07/05 07:29
2017/07/05 08:18
2017/07/05 16:07
2017/07/06 01:52 編集
2017/07/06 02:08 編集
2017/07/06 03:06 編集