Task.Factory.StartNew()は、呼び出し側のスレッドとは別のスレッドを使用することが保証されていますか?
質問
ある関数から新しいタスクを開始しますが、同じスレッドで実行させたくありません。異なるスレッドであれば、どのスレッドで実行されてもかまいません (従って この質問 で与えられた情報は役に立ちません)。
私は以下のコードが常に終了することを保証していますか?
TestLock
の前に
Task t
に再入力させることができますか?そうでない場合、再入力を防止するために推奨されるデザインパターンは何ですか?
object TestLock = new object();
public void Test(bool stop = false) {
Task t;
lock (this.TestLock) {
if (stop) return;
t = Task.Factory.StartNew(() => { this.Test(stop: true); });
}
t.Wait();
}
編集します。 Jon SkeetとStephen Toubによる以下の回答から、決定論的に再入力を防ぐ簡単な方法は、この拡張メソッドに示されているように、CancellationTokenを渡すことでしょう。
public static Task StartNewOnDifferentThread(this TaskFactory taskFactory, Action action)
{
return taskFactory.StartNew(action: action, cancellationToken: new CancellationToken());
}
どのように解決するのですか?
私は Stephen Toub にメールを送りました。 PFX チーム - にメールを送りました。彼は実に迅速に、そして多くの詳細とともに返信してくれました。大量の引用文を読むと、白黒のバニラよりも快適でなくなってしまうので、全部は引用していませんが、本当に、これは Stephen です - 私はこれほどのことは知りません:) 私はこの回答をコミュニティ wiki にして、以下のすべての良さは実際には私のコンテンツではないことを反映させました。
もしあなたが
Wait()
の上に タスク で完了した場合、ブロッキングは発生しません(もし、タスクが タスクステータス 以外のRanToCompletion
として返すか、さもなければ nop ). もしあなたがWait()
を呼び出すと、他に合理的にできることがないのでブロックしなければなりません(ブロックと言うとき、私は真のカーネルベースの待機とスピンの両方を含んでいます、それは通常両方の混合を行うからです)。 同様に、もしあなたがWait()
を呼び出した場合、そのタスクはCreated
またはWaitingForActivation
ステータスの場合、タスクが完了するまでブロックされます。これらはいずれも、議論されている興味深いケースではありません。興味深いケースは
Wait()
のタスクでWaitingToRun
の状態、つまり以前は タスクスケジューラ にキューイングされているが、その TaskScheduler は、まだ実際にタスクのデリゲートを実行するまでに至っていないことを意味します。 このような場合Wait
への呼び出しは、スケジューラーに、現在のスレッドでタスクを実行してもよいかどうかを尋ねます。TryExecuteTaskInline
メソッドを呼び出します。これは インライン化 . スケジューラは、タスクのインライン化のためにbase.TryExecuteTask
を呼び出すか、タスクを実行しないことを示すために'false'を返すかを選択できます(多くの場合、これは...のようなロジックで行われます)。return SomeSchedulerSpecificCondition() ? false : TryExecuteTask(task);
理由
TryExecuteTask
がブール値を返すのは、与えられたタスクが一度しか実行されないことを保証するために同期を処理するためです。) したがって、スケジューラが、タスクのインライン化を完全に禁止したい場合Wait
の間にタスクのインライン化を完全に禁止したい場合、それは単にreturn false;
もしスケジューラが可能な限り常にインライン化を許可したい場合は、次のように実装すればよいでしょう。return TryExecuteTask(task);
現在の実装(.NET 4 と .NET 4.5 の両方、そして個人的にはこれが変わるとは思っていません)では、ThreadPool をターゲットとするデフォルトのスケジューラは、現在のスレッドが ThreadPool のスレッドであり、そのスレッドが以前にタスクをキューに入れたものであればインライン化を可能にします。
デフォルトのスケジューラーは、タスクを待っているときに任意のスレッドをポンピングしないという点で、ここには任意のリエントランシーがないことに注意してください...それはそのタスクがインライン化されることだけを許可し、もちろんそのタスクが今度は行うことを決定したインライン化も許可します。 また、以下の点にも注意してください。
Wait
は特定の条件下ではスケジューラに問い合わせることすらせず、 代わりにブロックすることを好みます。 例えば、キャンセル可能な キャンセル・トークン を渡した場合、あるいは、無限でないタイムアウトを渡した場合、タスクの実行をインライン化するのに恣意的に長い時間がかかり、オール・オア・ナッシングとなり、キャンセル要求やタイムアウトを大幅に遅らせることになりかねないので、インライン化を試みないでしょう。 全体として、TPLは、タスクの実行を行うスレッドを無駄にすることとの間で、適切なバランスを取ろうと試みています。Wait
'ingとそのスレッドを再利用しすぎています。 この種のインライン化は、再帰的な分割統治問題(例えば クイックソート など) で、複数のタスクを生成し、それらがすべて完了するのを待つような再帰的な分割統治問題では、この種のインライン化は本当に重要です。インライン化せずにこのような処理を行った場合、プール内のすべてのスレッドと、将来提供されるスレッドを使い果たすため、非常に迅速にデッドロックが発生します。から分離された
Wait
を使うことも(リモートで)可能です。 タスク.ファクトリー.スタートニュー の呼び出しは、使用されるスケジューラが QueueTask 呼び出しの一部としてタスクを同期的に実行することを選択した場合、その場でタスクを実行してしまう可能性があります。 .NET に組み込まれているスケジューラーのどれもこれを行うことはなく、個人的にはスケジューラーとしては悪い設計だと思いますが、理論的には可能です。protected override void QueueTask(Task task, bool wasPreviouslyQueued) { return TryExecuteTask(task); }
のオーバーロードは
Task.Factory.StartNew
を受け入れないTaskScheduler
のスケジューラを使用します。TaskFactory
の場合、スケジューラはTask.Factory
はTaskScheduler.Current
. これは、もしあなたがTask.Factory.StartNew
をキューに入れたタスクの中から、この神話的なRunSynchronouslyTaskScheduler
にキューイングされている場合、それはまたRunSynchronouslyTaskScheduler
にもキューイングされ、その結果StartNew
の呼び出しは同期的にタスクを実行します。 もし、このことが気になるのであれば(例えば、ライブラリを実装していて、どこから呼ばれるかわからない場合)、明示的にTaskScheduler.Default
をStartNew
を呼び出すにはTask.Run
(これは常にTaskScheduler.Default
になる)、あるいはTaskFactory
をターゲットとして作成されたTaskScheduler.Default
.
EDIT: なるほど、完全に私の勘違いだったようで、現在タスク待ちのスレッドが は を乗っ取ることができます。これが起こるより簡単な例です。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1 {
class Program {
static void Main() {
for (int i = 0; i < 10; i++)
{
Task.Factory.StartNew(Launch).Wait();
}
}
static void Launch()
{
Console.WriteLine("Launch thread: {0}",
Thread.CurrentThread.ManagedThreadId);
Task.Factory.StartNew(Nested).Wait();
}
static void Nested()
{
Console.WriteLine("Nested thread: {0}",
Thread.CurrentThread.ManagedThreadId);
}
}
}
サンプル出力です。
Launch thread: 3
Nested thread: 3
Launch thread: 3
Nested thread: 3
Launch thread: 3
Nested thread: 3
Launch thread: 3
Nested thread: 3
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
ご覧の通り、待機中のスレッドが新しいタスクの実行に再利用されることがたくさんあります。これはスレッドがロックを獲得している場合でも起こり得ます。不愉快なリエントランシー。それなりにショックで心配です :(
関連
-
[解決済み] 保護レベルによりアクセス不能になりました。
-
[解決済み】クロススレッド操作が有効でない。作成されたスレッド以外のスレッドからアクセスされたコントロール
-
[解決済み] 2つのリストを結合する
-
[解決済み】Android "ビュー階層を作成した元のスレッドだけが、そのビューに触れることができる"
-
[解決済み] 他のスレッドからGUIを更新するにはどうすればよいですか?
-
[解決済み] プロセスとスレッドの違いは何ですか?
-
[解決済み] IDisposable インターフェースの正しい使用法
-
[解決済み] C#でベースコンストラクタを呼び出す
-
[解決済み] タスクとスレッドの違いは何ですか?
-
[解決済み】なぜFunc<T>ではなくExpression<Func<T>を使うのですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] [Solved] 1つ以上のエンティティで検証に失敗しました。詳細は'EntityValidationErrors'プロパティを参照してください [重複]。
-
[解決済み】「未割り当てのローカル変数を使用」とはどういう意味ですか?
-
[解決済み】ここで「要求URIに一致するHTTPリソースが見つかりませんでした」となるのはなぜですか?
-
[解決済み】値が期待した範囲に収まらない
-
[解決済み] [Solved] アセンブリ System.Web.Extensions dll はどこにありますか?
-
[解決済み] EntityTypeにキーが定義されていないエラー
-
[解決済み】"指定されたパスのフォーマットはサポートされていません。"
-
[解決済み】5.7.57 SMTP - MAIL FROMエラー時に匿名メールを送信するためにクライアントが認証されない
-
[解決済み】Microsoft.Extensions.LoggingからILoggerを解決することができない
-
[解決済み] ThreadPool.QueueUserWorkItem vs Task.Factory.StartNew