System.Threading.Tasks.Task task = System.Threading.Tasks.Task.Run(() => { // 別スレッドでの処理 });
Taskクラスで作成したスレッドは、既定でバックグラウンド スレッドで実行されます。c# 4.0 - Are Tasks created as background threads? - Stack Overflow
public Task( Action action )Task(Action) - Task コンストラクター (System.Threading.Tasks) | Microsoft Learn コンストラクター - Task クラス (System.Threading.Tasks) | Microsoft Learn
コンストラクタを呼ぶ以外に、TaskFactory.StartNew()やTask.Run()で生成する方法もあります。
Action action = () => { Console.Write("Task ID:{0}", Task.CurrentId); }; // インスタンス化し、開始する Task task1 = Task.Factory.StartNew(action); // インスタンス化し、開始する (StartNew()の代替) Task task2 = Task.Run(action); // コンストラクタでインスタンス化する。そしてStart()で開始する Task task3 = new Task(action); task3.Start(); // コンストラクタでインスタンス化する。そしてRunSynchronously()でメインスレッドで同期実行する Task task4 = new Task(action); task4.RunSynchronously();Task instantiation - Task Class (System.Threading.Tasks) | Microsoft Learn
Task.Run()にメソッド名で渡すとき、newを省略した書式ではFunc<Task>とのあいまいさを排除するために型名を指定します。
Task.Run(new Action(Method)); // OK Task.Run( (Action)Method); // OK Task.Run( Method); // error CS0121: 次のメソッドまたはプロパティ間で呼び出しが不適切です: 'Task.Run(Action)' と 'Task.Run(Func<Task>)'
デリゲートに値を渡す場合には、次のようにします。
Action<object> action = (obj) => { }; Task task1 = Task.Factory.StartNew(action, 1); // Task task2 = Task.Run(action, 2); // 不可 Task task3 = new Task(action, 3); task3.Start(); // 渡した値は、AsyncStateプロパティから取得できる object obj1 = task1.AsyncState; // 1 object obj3 = task3.AsyncState; // 3
扱う値が複数あるならば、Tupleクラス (.NET Fremework 4.7以降ならばValueTuple構造体) を用いると簡単です。Tuple クラス (System) | Microsoft Learn
Action<object> action = (obj) => { Tuple<int, string> tuple = (Tuple<int, string>)obj; int num = tuple.Item1; string str = tuple.Item2; }; Task task = Task.Factory.StartNew(action, Tuple.Create(1, "a"));
デリゲートから値を受け取る場合には、Task<TResult>クラスを用います。Task<TResult> クラス (System.Threading.Tasks) | Microsoft Learn
Func<int> action = () => { return 1; }; // Task<int> task1 = Task.Factory.StartNew(action); int result1 = task1.Result; // 結果が返されるまで、呼び出し元のスレッドはブロックされる // Task<int> task2 = Task.Run(action); int result2 = task2.Result; // Task<int> task3 = new Task<int>(action); task3.Start(); int result3 = task3.Result;
このとき呼び出し元のスレッドをブロックしないようにするには、awaitで結果を得ます。
Task<int> task1 = Task.Factory.StartNew(action); int result1 = await task1;
デリゲートに値を渡して受け取る場合には、次のようにします。
Func<object, int> action = (obj) =>
{
return 1;
};
//
Task<int> task1 = Task.Factory.StartNew(action, 1);
int result1 = task1.Result;
...
型 | プロパティ | |
---|---|---|
AggregateException | Exception | Taskが早く終了する原因を表すAggregateException |
TaskFactory | Factory | ファクトリ メソッド |
int | Id | Taskに割り当てられた識別子。作成順の番号となるとは限らず、一意であることも保証されない |
Task | CompletedTask | StatusがRanToCompletionなタスク |
TaskStatus | Status | タスクの状態 |
bool | IsCompleted | trueならば、Taskが完了している。完了とはStatusがRanToCompletion、Faulted、Canceledの3つの状態のいずれか |
bool | IsCanceled | trueならば、Taskがキャンセルされたことによって完了している。これは次のいずれかの状況を表す。
|
object? | AsyncState | Taskが作成されたときに供給された状態オブジェクト。このオブジェクトは、TaskコンストラクタやTaskFactory.StartNew()メソッドで供給できる |
列挙子 | 数値 | 意味 |
---|---|---|
Created | 0 | タスクは初期化されているが、まだスケジュールされていない |
WaitingForActivation | 1 | タスクはアクティブ化されるのを待機中で、.NET Frameworkインフラストラクチャによって内部的にスケジュールされている
これはTask.ContinueWith()で作成されたタスク About continuations - Chaining tasks using continuation tasks - .NET | Microsoft Learn |
WaitingToRun | 2 | タスクの実行はスケジュールされているが、まだ開始されていない |
Running | 3 | タスクは実行中で、まだ完了していない |
WaitingForChildrenToComplete | 4 | タスクは終了し、アタッチされている子タスクの完了を無条件に待機している |
RanToCompletion | 5 | タスクは正常に完了した |
Canceled | 6 | タスクのCancellationTokenがシグナル状態であるときに、タスクがこのトークンを使用してOperationCanceledExceptionをスローすることで取り消しを認めたか、タスクの実行開始前にタスクのCancellationTokenが既にシグナル状態だった |
Faulted | 7 | タスクはハンドルされていない例外のために終了した |
戻り値 | メソッド | 機能 |
---|---|---|
ConfiguredTaskAwaitable | ConfigureAwait(Boolean) | Taskを待機する方法を構成する |
Task | ContinueWith(Action<Task>, TaskContinuationOptions) | Taskが完了したときに実行される、継続タスクを作成できる |
Task | Delay(Int32) | 遅延した後に完了する、タスクを作成する |
Task | Run(Action) | 指定の処理をスレッドプールに並ばせ、その処理を表すTaskを返す。TaskScheduler.Defaultを用いる |
void | Start() | タスクを開始し、TaskScheduler.Currentにその実行を予定する |
void | RunSynchronously() | TaskScheduler.Currentで同期的にタスクを実行する |
void | Wait() | タスクの実行が完了するまで、待機する |
bool | Wait(Int32) | タスクの実行が完了するまで、指定時間待機する |
void | WaitAll(Task[]) | すべてのタスクの実行が完了するまで、待機する |
int | WaitAny(Task[]) | いずれかのタスクの実行が完了するまで、待機する |
Task | WhenAll(Task[]) | すべてのタスクの実行が完了したときに完了する、タスクを作成する |
Task<Task> | WhenAny(Task[]) | いずれかのタスクの実行が完了したときに完了する、タスクを作成する |
.NET Framework 4.5以降は、引数や細かい制御が不要ならばTaskFactory.StartNew()の代わりにこのメソッドを用います。逆にそれらが必要ならば、StartNew()を用います。Remarks - TaskFactory.StartNew Method (System.Threading.Tasks) | Microsoft Learn
public static Task Run( Action action )Run(Action) - Task.Run メソッド (System.Threading.Tasks) | Microsoft Learn
ただしStartNew()とRun()では、既定の設定が異なります。Task.Run vs Task.Factory.StartNew - .NET Parallel Programming - Site Home - MSDN Blogs
public Task StartNew(Action action) { StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; Task currTask = Task.InternalCurrent; return Task.InternalStartNew(currTask, action, null, m_defaultCancellationToken, GetDefaultScheduler(currTask), m_defaultCreationOptions, InternalTaskOptions.None, ref stackMark); }StartNew - TaskFactory.cs
public static Task Run(Action action) { StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; return Task.InternalStartNew(null, action, null, default(CancellationToken), TaskScheduler.Default, TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, ref stackMark); }Run - Task.cs
よってRun()と同等の処理を期待するならば、引数を明示します。StartNew is Dangerous
Task.Factory.StartNew(() => { }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
StartNew()では引数を指定しないとTaskScheduler.Currentが用いられるため、コンテキストによっては呼び出し元と同じスレッドで実行されます。たとえば同期コンテキストに関連付けられたTask内で実行したときは、同じとなります。
// (ここはUIスレッド) int id1, id2, id3, id4, id5, id6, id7, id8, id9; id1 = Thread.CurrentThread.ManagedThreadId; // 10 await Task.Run(() => { id2 = Thread.CurrentThread.ManagedThreadId; // 6 }).ContinueWith((_task) => { id3 = Thread.CurrentThread.ManagedThreadId; // 10 // Task.Run() で開始 Task.Run(() => { id4 = Thread.CurrentThread.ManagedThreadId; // 6 }); // TaskFactory.StartNew() で開始 Task.Factory.StartNew(() => { id5 = Thread.CurrentThread.ManagedThreadId; // 10 (呼び出し元と同じスレッド) }); Task.Factory.StartNew(() => // TaskScheduler.Default を指定 { id6 = Thread.CurrentThread.ManagedThreadId; // 11 }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); // Task.Start() で開始 new Task(() => { id7 = Thread.CurrentThread.ManagedThreadId; // 10 (呼び出し元と同じスレッド) }).Start(); new Task(() => // TaskScheduler.Default を指定 { id8 = Thread.CurrentThread.ManagedThreadId; // 6 }).Start(TaskScheduler.Default); // Thread.Start() で開始 new Thread(() => { id9 = Thread.CurrentThread.ManagedThreadId; // 16 }).Start(); }, TaskScheduler.FromCurrentSynchronizationContext()); // 同期コンテキストでの実行を指示
指定の処理をスレッドプールに並ばせ、その処理を表すTask<TResult>を受け取れます。
public static Task<TResult> Run<TResult>( Func<TResult> function // 非同期に実行する処理 )Run<TResult>(Func<TResult>) - Task.Run メソッド (System.Threading.Tasks) | Microsoft Learn
戻り値のTask<TResult>.Resultから、結果を得られます。
FromResult()は、結果だけを返すときに用います。How to: Create Pre-Computed Tasks | Microsoft Learn .net - What is the use for Task.FromResult<TResult> in C# - Stack Overflow
public static Task<TResult> FromResult<TResult>(
TResult result // 完了したタスクに格納する結果
)
Task.FromResult(TResult) メソッド (TResult) (System.Threading.Tasks) | MSDN
int cache = 0; public Task<int> Method1() { if (cache != 0) { // return cache * 2; // error CS0029: 型 'int' を 'System.Threading.Tasks.Task<int>' に暗黙的に変換できません return Task.FromResult(cache * 2); } else { return Task.Run(() => { cache = 10; return cache; }); } } public async void Method2() { int a1 = await Method1(); // 10が返される int a2 = await Method1(); // 20が返される }
public void Start (System.Threading.Tasks.TaskScheduler scheduler);Start(TaskScheduler) - Task.Start メソッド (System.Threading.Tasks) | Microsoft Learn
schedulerで、Taskに関連付けるTaskSchedulerを指定できます。これを省略するとTaskScheduler.Currentが指定されます。Start - Task.cs
Taskを実行できるのは1度だけです。くり返し実行すると「既に開始したタスクで Start を呼び出すことはできません。」や「完了したタスクで Start を呼び出すことはできません。」としてInvalidOperationExceptionが投げられます。Remarks - Task.Start Method (System.Threading.Tasks) | Microsoft Learn
Task task = new Task(() => { });
task.Start();
task.Start(); // 「既に開始したタスクで Start を呼び出すことはできません。」
Task task = new Task(() => { });
task.Start();
task.Wait();
task.Start(); // 「完了したタスクで Start を呼び出すことはできません。」
Task.Delay(1).Start(); // 「保証型のタスクで Start を呼び出すことはできません。」
タスクが完了するまで、呼び出し元のスレッドをブロックします。
public void Wait()Wait() - Task.Wait メソッド (System.Threading.Tasks) | Microsoft Learn
これにより呼び出し元のスレッドと同期させられます。なお完了前にタスクがキャンセルされたときには、AggregateException例外が投げられます。
Task task = Task.Run(() => { }); task.Wait(); // 呼び出し元のスレッドをブロックし、タスクの完了まで待機する // これ以降はタスクの完了後に処理される
このメソッドの呼び出し前にタスクが完了していると、待ちません。一方で開始されていないと、永久に待ちます。Task.Wait and “Inlining” - .NET Parallel Programming
Task task = new Task(() => { }); task.Wait();
スレッドがブロックされることを望まず非同期で処理するならば、awaitで待機させます。c# - await vs Task.Wait - Deadlock? - Stack Overflow
await Task.Run(() => // 呼び出し元のスレッドはブロックされない { }); // これ以降はタスクの完了後に処理される
非同期メソッドが値を返すならば、Task.Wait()ではなくTask<TResult>.Resultプロパティのgetアクセサにアクセスすることで同期させられます。Remarks - Task<TResult>.Result Property (System.Threading.Tasks) | Microsoft Learn
Task<int> task = Task.Run(() =>
{
return 1;
});
// 呼び出し元のスレッドをブロックし、タスクの完了まで待機する
int result = task.Result;
public void Wait (System.Threading.CancellationToken cancellationToken);Wait(CancellationToken) - Task.Wait Method (System.Threading.Tasks) | Microsoft Learn
引数にCancellationTokenをとるWait()では、タスクが完了するか、キャンセル トークン (cancellation token) がキャンセルされるまで待機します。キャンセル トークンがキャンセルされたときには、このメソッドはOperationCanceledExceptionを投げます。
public bool Wait (int millisecondsTimeout);
指定時間内にタスクが完了しないと、falseが返されます。またタスクがキャンセルされるとInnerExceptionsにTaskCanceledExceptionを含む、AggregateExceptionが投げられます。
すべてのタスクが完了するまで、呼び出し元のスレッドをブロックします。
public static void WaitAll (params System.Threading.Tasks.Task[] tasks);WaitAll(Task[]) - Task.WaitAll メソッド (System.Threading.Tasks) | Microsoft Learn
Task task1 = Task.Run(() => { });
Task task2 = Task.Run(() => { });
Task.WaitAll(task1, task2); // すべてのタスクが完了するまで、ここで待機する
tasksにnullが含まれていると、「タスク配列に少なくとも 1 つの null 要素が含まれました。」としてArgumentExceptionが投げられます。そのような状況が生じる恐れがあるならば、実行が不要な要素はTask.CompletedTaskを代入しておきます。
すべてのタスクが完了したときに完了するタスクを得られます。
public static System.Threading.Tasks.Task WhenAll (params System.Threading.Tasks.Task[] tasks);WhenAll(Task[]) - Task.WhenAll メソッド (System.Threading.Tasks) | Microsoft Learn
Task task1 = Task.Run(() => { });
Task task2 = Task.Run(() => { });
await Task.WhenAll(task1, task2);
// すべてのタスクが完了した後に、ここが処理される
Taskが完了したときに実行される、継続タスク (continuation task) を作成できます。この継続タスクはコンテキストによっては新しいスレッドで実行されます。
int id1, id2, id3; id1 = Thread.CurrentThread.ManagedThreadId; // 10 Task.Run(() => { id2 = Thread.CurrentThread.ManagedThreadId; // 6 }).ContinueWith((_task) => { id3 = Thread.CurrentThread.ManagedThreadId; // 12 });
継続タスクを作成前にTaskが完了していても、実行されます。
Task task = new Task(() => { }); task.Start(); task.Wait(); // Taskは完了する task.ContinueWith((_task) => { // 実行される });
public System.Threading.Tasks.Task ContinueWith ( Action<System.Threading.Tasks.Task> continuationAction );
実行するアクションしか指定しなかったときは、TaskSchedulerはCurrent、CancellationTokenはdefault(CancellationToken)、TaskContinuationOptionsにはNoneが指定されます。ContinueWith - Task.cs
public System.Threading.Tasks.Task ContinueWith ( Action<System.Threading.Tasks.Task> continuationAction, // 実行するアクション System.Threading.Tasks.TaskContinuationOptions continuationOptions // タスクの動作条件 );ContinueWith(Action<Task>, TaskContinuationOptions) - Task.ContinueWith Method (System.Threading.Tasks) | Microsoft Learn
continuationOptionsを省略するとTaskContinuationOptions.Noneが指定さます。また戻り値は、タスクが完了したときにcontinuationOptionsの条件が満たされるならば実行されるTaskです。
Task.Run(() =>
{
}).ContinueWith((_task) =>
{
// 捕捉されない例外があったときに、ここが実行される
}, TaskContinuationOptions.OnlyOnFaulted);
列挙子 | 値 | 内容 |
---|---|---|
None | 0 | 既定の動作。タスクが完了したとき、Statusプロパティの値は無関係にアクションが実行される |
OnlyOnFaulted | 327680 | 捕捉されない例外があったときに、アクションが実行される |
OnlyOnRanToCompletion | 393216 | タスクが完了したときにStatusプロパティがRanToCompletionのときに、アクションが実行される |
継続タスクを実行するスケジューラを指定できます。
public System.Threading.Tasks.Task ContinueWith ( Action<System.Threading.Tasks.Task> continuationAction, System.Threading.Tasks.TaskScheduler scheduler // 継続タスクに関連付けられ、その実行に用いられるTaskScheduler );ContinueWith(Action<Task>, TaskScheduler) - Task.ContinueWith Method (System.Threading.Tasks) | Microsoft Learn
// (ここはUIスレッド) int id1, id2, id3; id1 = Thread.CurrentThread.ManagedThreadId; // 10 Task.Run(() => { id2 = Thread.CurrentThread.ManagedThreadId; // 6 }).ContinueWith((_task) => { id3 = Thread.CurrentThread.ManagedThreadId; // 10 (呼び出し元と同一のスレッド) }, TaskScheduler.FromCurrentSynchronizationContext());
Taskをawaitで待機するのに使用される、awaiterを設定できます。
public ConfiguredTaskAwaitable ConfigureAwait( bool continueOnCapturedContext )Task.ConfigureAwait メソッド (System.Threading.Tasks) | Microsoft Learn
continueOnCapturedContextをfalseとすると、Taskの待機後に呼び出し元のスレッドに戻されなくなります。
await Task.Run(() => { // ここは別スレッド }).ConfigureAwait(false); // 呼び出し元へは戻されず、Task内と同一のスレッド。よってTaskの処理をインラインで記述するならば、そこの末尾に記述しても同じ
ConfigureAwait()を呼ばないのはConfigureAwait(true)として呼び出すのと同じで、非同期コンテキスト (async context) によってはTaskの待機後に呼び出し元のスレッドに戻されます。
このConfigureAwait()を呼ばない場合の既定の動作はパフォーマンス上のコストや、UIスレッドでのデッドロックを発生させる恐れがあります。よってその必要性がないならば、このメソッドでfalseを指定します。 Task.ConfigureAwait Method (System.Threading.Tasks) | Microsoft Learn ConfigureAwait FAQ - .NET Blog
await Task.Run(() => { // ここは別スレッド }); // 非同期コンテキストによっては、呼び出し元のスレッドに戻される
awaitで処理を戻すTaskの完了を、Wait()で待たせるとデッドロックします。
Func<Task> func = async () => { await Task.Delay(1); // awaitがあるため呼び出し元のスレッドへ戻そうとするが、 // そのスレッドはこのスレッドの完了をWait()で待機しているため、戻せない }; func().Wait();
Func<Task> func = async () =>
{
await Task.Delay(1).ConfigureAwait(false);
// ここはTaskと同一のスレッドで実行されるため、このままこのスレッドの処理は完了する
};
func().Wait();
c# - Any difference between "await Task.Run(); return;" and "return Task.Run()"? - Stack Overflow
ConfigureAwait(true)ではコンテキストによっては呼び出し元のスレッドに戻されないことがあるため、より確実にはスケジューラを指定するContinueWith()を用います。
await Task.Run(() => { }).ContinueWith((_task) => { // ここは呼び出し元のスレッド }, TaskScheduler.FromCurrentSynchronizationContext()).ConfigureAwait(false); // Task内と同一のスレッド
タスクが完了しているならば呼び出し元のスレッドに戻す必要がないため、ConfigureAwait(false)でもスレッドが変更されません。
int id1 = Thread.CurrentThread.ManagedThreadId; // 10 await Task.Delay(100).ConfigureAwait(false); int id2 = Thread.CurrentThread.ManagedThreadId; // 12
int id1 = Thread.CurrentThread.ManagedThreadId; // 10 await Task.CompletedTask.ConfigureAwait(false); int id2 = Thread.CurrentThread.ManagedThreadId; // 10 (スレッドが変更されていない)
単体テストから実行した場合、ConfigureAwait(true)以降も呼び出し元のスレッドに戻らないことがあります。c# - Async does not continue on same thread in unit test - Stack Overflow
int id1, id2, id3; id1 = Thread.CurrentThread.ManagedThreadId; // 9 await Task.Run(() => { id2 = Thread.CurrentThread.ManagedThreadId; // 10 }).ConfigureAwait(true); id3 = Thread.CurrentThread.ManagedThreadId; // 10 (ここでは非同期メソッドと同一のスレッドとなっている)
指定時間後に完了するタスクを作成できます。
public static System.Threading.Tasks.Task Delay (int millisecondsDelay);Delay(Int32) - Task.Delay メソッド (System.Threading.Tasks) | Microsoft Learn
現在のスレッドをブロックしても良いならば、Wait()で待ちます。これはThread.Sleep()と同等です。
Task.Delay(1000).Wait();
ブロックしないならば、awaitで制御を戻します。
await Task.Delay(1000);
遅延中にキャンセルできるようにするには、CancellationTokenを渡します。
CancellationTokenSource tokenSource = new CancellationTokenSource(); Task.Run(async () => { await Task.Delay(1000, tokenSource.Token); // キャンセルされると「タスクが取り消されました。」としてTaskCanceledExceptionが投げられる // キャンセルされると、ここには到達しない }); // 時間のかかる処理 tokenSource.Cancel(); // キャンセルを要求するDelay(Int32, CancellationToken) - Task.Delay メソッド (System.Threading.Tasks) | Microsoft Learn
public void Dispose ();Task.Dispose Method (System.Threading.Tasks) | Microsoft Learn
Dispose()を呼び出せるのは、StatusプロパティがRanToCompletion、Faulted、またはCanceledの状態であるとき、つまりIsCompletedがtrueのときのみです。それ以外のときにDispose()を呼ぶと、「タスクを破棄できるのは、そのタスクが完了状態 (RanToCompletion、Faulted、または Canceled) の場合だけです。(A task may only be disposed if it is in a completion state (RanToCompletion, Faulted or Canceled).)」としてInvalidOperationExceptionが投げられます。
基本的にDispose()を呼び出す必要はありません。Do I need to dispose of Tasks? | Parallel Programming with .NET
キャンセルを要求する側はCancellationTokenSource.Cancel()を呼び出し、別スレッドの側はそれのCancellationToken.IsCancellationRequestedでキャンセルの要求を検出し処理を中断します。
CancellationTokenSource tokenSource = new CancellationTokenSource(); CancellationToken token = tokenSource.Token; Task.Run(() => { if (token.IsCancellationRequested) { // キャンセルを要求されたならば、処理を抜ける break; } }); // キャンセルを要求する tokenSource.Cancel();
一度キャンセルを要求したCancellationTokenSourceはその要求を取り消せないため、再度CancellationTokenSourceが必要なときはそのインスタンスを再生成します。
CancellationTokenSourceへの最後の参照を解放する前に、CancellationTokenSource.Dispose()を呼びます。さもなくばガベージ コレクタがFinalize()を呼ぶまで、そのリソースは解放されません。 Dispose() - CancellationTokenSource.Dispose Method (System.Threading) | Microsoft Learn c# - When to dispose CancellationTokenSource? - Stack Overflow
ただしDispose()の後にCancel()を呼ぶとObjectDisposedExceptionが投げられるため、それへの対処が必要です。
キャンセルしたことを呼び出し側へ通知したいならば、OperationCanceledExceptionを投げることでタスクを終了させます。そうするとStatusプロパティはTaskStatus.Canceledとなり、TaskContinuationOptions.OnlyOnCanceledを指定したContinueWith()で継続タスクを実行できます。
OperationCanceledExceptionはタスクのスレッドから投げられないと捕捉されず、継続タスクも実行されません。
CancellationToken.ThrowIfCancellationRequested()を呼び出してキャンセルさせる方法もありますが、これは
if (token.IsCancellationRequested) throw new OperationCanceledException(token);
とすることと同義です。CancellationToken.ThrowIfCancellationRequested Method (System.Threading) | Microsoft Learn
OperationCanceledExceptionが投げられるTaskは、そのコンストラクタでCancellationTokenを渡します。これはキャンセルできたことをTask.IsCanceledで検証するときにも有用ですが、これらが不要ならば渡す必要はありません。c# - Cancellation token in Task constructor: why? - Stack Overflow
public Task ( Action action, System.Threading.CancellationToken cancellationToken );Task(Action, CancellationToken) - Task Constructor (System.Threading.Tasks) | Microsoft Learn
CancellationTokenをTaskのコンストラクタで渡さないとキャンセルと見なされずStatusはFaultedとなり、継続タスクはOnlyOnCanceledではなくOnlyOnFaultedが実行されます。
CancellationTokenSource tokenSource = new CancellationTokenSource(); CancellationToken token = tokenSource.Token; Task task = new Task(() => { token.ThrowIfCancellationRequested(); // token.IsCancellationRequested が true ならば、 // OperationCanceledExceptionが投げられる }, token); // CancellationTokenを渡さねばならない task.ContinueWith((_task) => { // キャンセルされたときに、ここが実行される }, TaskContinuationOptions.OnlyOnCanceled);
CancellationTokenSourceを作成するときに時間を指定すると、一定時間後に自動でキャンセルが要求されるようにできます。
キャンセルが要求されるだけで、指定の時間でキャンセルされるわけではありません。
public CancellationTokenSource (int millisecondsDelay);CancellationTokenSource(Int32) - CancellationTokenSource Constructor (System.Threading) | Microsoft Learn
コンストラクタの呼び出し時点から秒読みが開始され、指定の時間が経過するとToken.IsCancellationRequestedがtrueに設定されます。この時間は、CancellationTokenSource.CancelAfter()で後から変更できます。
int millisecondsDelay = 100; CancellationTokenSource tokenSource = new CancellationTokenSource(millisecondsDelay); CancellationToken token = tokenSource.Token; Task task = new Task(() => { if (token.IsCancellationRequested) { } });
連続する要求に対し、最後の1つだけを処理する方法を考えます。
CancellationTokenSource tokenSource = null; void Method() { CancellationTokenSource newTokenSource = new CancellationTokenSource(); CancellationTokenSource oldTokenSource = Interlocked.Exchange(ref tokenSource, newTokenSource); if (oldTokenSource != null) { oldTokenSource.Cancel(); // キャンセルを要求する } try { // 待機する await Task.Delay(1000, newTokenSource.Token); // キャンセルが要求されたならばTaskCanceledExceptionが投げられる // 何らかの処理 } catch (TaskCanceledException) { } }