Task.Factory.StartNew(...)を使用せずに、代わりにTask.Run(...)または同様のものを使用せずに、実行中の非同期タスクを設定する方法が必要です。
環境:
「長期実行」として設定する(つまり、専用スレッドを与える)ために外部でキャンセルされるまで継続的にループするタスクがあります。これは、以下のコードによって実現できます。
var cts = new CancellationTokenSource();
Task t = Task.Factory.StartNew(
async () => {
while (true)
{
cts.Token.ThrowIfCancellationRequested();
try
{
"Running...".Dump();
await Task.Delay(500, cts.Token);
}
catch (TaskCanceledException ex) { }
} }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
問題は、Task.Factory.StartNew(...)が、渡されたアクティブな非同期タスクではなく、機能的に常にtaskStatusが「RanToCompletion」の「アクションを実行するタスク」を返すことです。私のコードはタスクのステータスを追跡して「キャンセル」(または「障害」)になる時期を確認できる必要があるため、以下のようなものを使用する必要があります。
var cts = new CancellationTokenSource();
Task t = Task.Run(
async () => {
while (true)
{
cts.Token.ThrowIfCancellationRequested();
try
{
"Running...".Dump();
await Task.Delay(500, cts.Token);
}
catch (TaskCanceledException ex) { }
} }, cts.Token);
Task.Run(...)は、必要に応じて、非同期プロセス自体を返し、「キャンセル」または「障害」の実際のステータスを取得できます。ただし、実行時間の長いタスクは指定できません。それで、誰かがそのアクティブなタスク自体を(望ましいtaskStatusで)保存し、タスクを長時間実行に設定しながら、非同期タスクを実行する最善の方法を知っていますか?
Task.Factory.StartNew
から返されたタスクでUnwrap
を呼び出すと、正しいステータスの内部タスクが返されます。
var cts = new CancellationTokenSource();
Task t = Task.Factory.StartNew(
async () => {
while (true)
{
cts.Token.ThrowIfCancellationRequested();
try
{
"Running...".Dump();
await Task.Delay(500, cts.Token);
}
catch (TaskCanceledException ex) { }
} }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap();
外部で取り消されるまで継続的にループするタスクがあり、「長時間実行」に設定したい(つまり、専用スレッドを与えたい)...非同期タスクを実行しながら、両方のアクティブタスクを保存する方法それ自体(望ましいtaskStatusを使用)およびタスクを長時間実行に設定しますか?
これにはいくつかの問題があります。まず、「長時間実行」は必ずしも専用スレッドを意味するわけではありません。それは、TPLにタスクが長時間実行されているというヒントを与えていることを意味します。現在の(4.5)実装では、専用のスレッドを取得します。ただし、これは保証されておらず、将来変更される可能性があります。
したがって、専用スレッドを必要とする場合は、専用スレッドを作成する必要があります。
もう1つの問題は、「非同期タスク」の概念です。スレッドプールで実行されているasync
コードで実際に発生することは、非同期操作(つまり、スレッドががスレッドプールに返されることです(つまり、 、Task.Delay
)が進行中です。次に、非同期操作が完了すると、スレッドプールからスレッドが取得され、async
メソッドが再開されます。一般的なケースでは、このタスクを完了するためにスレッドを予約するよりも効率的です。
したがって、async
タスクがスレッドプールで実行されている場合、専用スレッドは実際には意味がありません。
解決策について:
async
コードを実行するために専用スレッドを必要とする場合は、 AsyncContextThread
AsyncExライブラリから :
using (var thread = new AsyncContextThread())
{
Task t = thread.TaskFactory.Run(async () =>
{
while (true)
{
cts.Token.ThrowIfCancellationRequested();
try
{
"Running...".Dump();
await Task.Delay(500, cts.Token);
}
catch (TaskCanceledException ex) { }
}
});
}
ただし、ほとんどの場合、専用のスレッドは必要ありません。コードがスレッドプールで実行できる場合は、おそらく実行する必要があります。専用スレッドは、スレッドプールで実行されているasync
メソッドには意味がありません。より具体的には、長期実行フラグは、スレッドプールで実行されているasync
メソッドには意味がありません。
別の言い方をすると、async
ラムダを使用して、スレッドプールが実際に実行する(およびタスクとして表示される)ものはawait
ステートメント間のラムダの部分。これらのパーツは長期実行されないため、長期実行フラグは必要ありません。そしてあなたの解決策は次のようになります:
Task t = Task.Run(async () =>
{
while (true)
{
cts.Token.ThrowIfCancellationRequested(); // not long-running
try
{
"Running...".Dump(); // not long-running
await Task.Delay(500, cts.Token); // not executed by the thread pool
}
catch (TaskCanceledException ex) { }
}
});
専用のスレッドでは、譲りたいことは何もありません。 async
とawait
は使用せず、同期呼び出しを使用してください。
この質問 は、await
なしでキャンセル可能なスリープを実行する2つの方法を提供します。
Task.Delay(500, cts.Token).Wait(); // requires .NET 4.5
cts.WaitHandle.WaitOne(TimeSpan.FromMilliseconds(500)); // valid in .NET 4.0 and later
作業の一部が並列処理を使用している場合は、並列タスクを開始してそれらを配列に保存し、Task.WaitAny
でTask[]
を使用できます。メインスレッドプロシージャではawait
はまだ使用されていません。
これは不要であり、タスクスケジューラは0.5秒を超えて実行された場合にタスクをLongRunningに設定するため、Task.Runで十分です。
理由はこちらをご覧ください。 https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html
カスタムTaskCreationOptionsを指定する必要があります。それぞれのオプションについて考えてみましょう。 AttachedToParentを非同期タスクで使用しないでください。 DenyChildAttachは常に非同期タスクで使用する必要があります(ヒント:まだ知らない場合は、StartNewは必要なツールではありません)。 DenyChildAttachはTask.Runによって渡されます。 HideSchedulerは、非常にあいまいなスケジューリングシナリオで役立つ場合がありますが、一般的に非同期タスクでは使用しないでください。これは、LongRunningとPreferFairnessのみを残します。どちらも、アプリケーションのプロファイリング後にのみ指定する必要がある最適化のヒントです。特にLongRunningが誤用されることがよくあります。大多数の状況では、スレッドプールは、0.5秒以内にLongRunningフラグなしで任意の長期実行タスクに調整します。ほとんどの場合、実際には必要ありません。
あなたがここで持っている本当の問題はあなたの操作は実際には長時間実行されていないです。あなたがしている実際の作業は非同期操作です、つまりそれは基本的に即座に呼び出し元に戻ります。したがって、タスクスケジューラにスケジュールを設定させるときに長時間実行するヒントを使用する必要がないだけでなく、この作業を行うためにスレッドプールスレッドを使用する必要さえありません。基本的に瞬間的になるでしょう。長期実行フラグはもちろん、StartNew
またはRun
を使用しないでください。
したがって、非同期メソッドを取得して別のスレッドでstartingするのではなく、非同期メソッドを呼び出すことで、現在のスレッドで直接開始できます。すでに非同期の操作のstartingをオフロードすると、moreの作業が作成され、が遅くなります。
したがって、コードは次のように簡略化されます。
var cts = new CancellationTokenSource();
Task t = DoWork();
async Task DoWork()
{
while (true)
{
cts.Token.ThrowIfCancellationRequested();
try
{
"Running...".Dump();
await Task.Delay(500, cts.Token);
}
catch (TaskCanceledException) { }
}
}
考慮すべきことは、スレッドが実行される時間の長さではなく、実際に機能している時間です。あなたの例では、短い作業とそれらのawait Task.Delay(...)
があります。これがプロジェクトで実際に当てはまる場合は、このタスク専用のスレッドを使用して、通常のスレッドプールで実行しないでください。 IOオペレーションまたはTask.Delay()
でawait
を呼び出すたびに、他のタスクが使用できるようにスレッドを解放します。
LongRunning
を使用する必要があるのは、スレッドプールからスレッドを減らし、決して返さないか、ごく一部の時間だけ返さない場合です。このような場合(作業が長く、Task.Delay(...)
が比較的短い)、ジョブに専用のスレッドを使用するのが妥当な解決策です。一方、スレッドがほとんどの時間実際に動作している場合は、システムリソース(CPU時間)を消費し、スレッドプールのスレッドを保持しているかどうかは、とにかく他の作業の発生を妨げているため、問題にはなりません。 。
結論?可能であれば、Task.Run()
(LongRunning
なし)を使用し、長期実行タスクでawait
を使用してください。 LongRunning
に戻すのは、他のアプローチが問題を引き起こしていることが実際にわかり、コードと設計をチェックして、本当に必要であり、コードに変更できるものが他にないことを確認してください。