Windows 8のWinRTタスクで遊んでいて、以下の方法でタスクをキャンセルしようとしているのですが、ある程度はうまくいきます。CancelNotificationメソッドが呼ばれ、タスクがキャンセルされたように見えますが、バックグラウンドでタスクは実行され続け、タスク完了後、タスクのステータスは常に完了し、キャンセルされたことはありません。タスクがキャンセルされたときに、タスクを完全に停止させる方法はないでしょうか?
private async void TryTask()
{
CancellationTokenSource source = new CancellationTokenSource();
source.Token.Register(CancelNotification);
source.CancelAfter(TimeSpan.FromSeconds(1));
var task = Task<int>.Factory.StartNew(() => slowFunc(1, 2), source.Token);
await task;
if (task.IsCompleted)
{
MessageDialog md = new MessageDialog(task.Result.ToString());
await md.ShowAsync();
}
else
{
MessageDialog md = new MessageDialog("Uncompleted");
await md.ShowAsync();
}
}
private int slowFunc(int a, int b)
{
string someString = string.Empty;
for (int i = 0; i < 200000; i++)
{
someString += "a";
}
return a + b;
}
private void CancelNotification()
{
}
Cancellation]1 (これは .NET 4.0 で導入され、それ以降ほとんど変更されていません) と Task-Based Asynchronous Pattern を読み、 CancellationToken
と async
メソッドの使い方についてガイドラインを用意します。
要約すると、キャンセルをサポートする各メソッドに CancellationToken
を渡し、そのメソッドは定期的にそれをチェックしなければならない、ということです。
private async Task TryTask()
{
CancellationTokenSource source = new CancellationTokenSource();
source.CancelAfter(TimeSpan.FromSeconds(1));
Task<int> task = Task.Run(() => slowFunc(1, 2, source.Token), source.Token);
// (A canceled task will raise an exception when awaited).
await task;
}
private int slowFunc(int a, int b, CancellationToken cancellationToken)
{
string someString = string.Empty;
for (int i = 0; i < 200000; i++)
{
someString += "a";
if (i % 1000 == 0)
cancellationToken.ThrowIfCancellationRequested();
}
return a + b;
}
あるいは、slowFunc
の修正を避けるため(例えば、ソースコードにアクセスできない場合など)。
var source = new CancellationTokenSource(); //original code
source.Token.Register(CancelNotification); //original code
source.CancelAfter(TimeSpan.FromSeconds(1)); //original code
var completionSource = new TaskCompletionSource<object>(); //New code
source.Token.Register(() => completionSource.TrySetCanceled()); //New code
var task = Task<int>.Factory.StartNew(() => slowFunc(1, 2), source.Token); //original code
//original code: await task;
await Task.WhenAny(task, completionSource.Task); //New code
また、https://github.com/StephenCleary/AsyncEx からの素敵な拡張メソッドを使うこともできますし、以下のようにシンプルに見せることもできます。
await Task.WhenAny(task, source.Token.AsTask());
カバーされていないケースの1つは、asyncメソッド内のキャンセルを処理する方法です。 たとえば、一部のデータをサービスにアップロードして何かを計算し、結果を返す必要がある単純なケースを考えてみます。
public async Task<Results> ProcessDataAsync(MyData data)
{
var client = await GetClientAsync();
await client.UploadDataAsync(data);
await client.CalculateAsync();
return await client.GetResultsAsync();
}
キャンセルをサポートする場合、最も簡単な方法は、トークンを渡して、各asyncメソッド呼び出しの間でキャンセルされたかどうかを確認することです(またはContinueWithを使用)。 通話が非常に長い場合は、キャンセルするまでしばらくお待ちください。 キャンセルするとすぐに失敗する小さなヘルパーメソッドを作成しました。
public static class TaskExtensions
{
public static async Task<T> WaitOrCancel<T>(this Task<T> task, CancellationToken token)
{
token.ThrowIfCancellationRequested();
await Task.WhenAny(task, token.WhenCanceled());
token.ThrowIfCancellationRequested();
return await task;
}
public static Task WhenCanceled(this CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs);
return tcs.Task;
}
}
したがって、それを使用するには、任意のasync呼び出しに .WaitOrCancel(token)
を追加します。
public async Task<Results> ProcessDataAsync(MyData data, CancellationToken token)
{
Client client;
try
{
client = await GetClientAsync().WaitOrCancel(token);
await client.UploadDataAsync(data).WaitOrCancel(token);
await client.CalculateAsync().WaitOrCancel(token);
return await client.GetResultsAsync().WaitOrCancel(token);
}
catch (OperationCanceledException)
{
if (client != null)
await client.CancelAsync();
throw;
}
}
これはあなたが待っていたタスクを止めず、実行し続けることに注意してください。 例の「CancelAsync」呼び出しなど、別のメカニズムを使用して停止する必要があります。または、同じ「CancellationToken」を「タスク」に渡して、最終的にキャンセルを処理できるようにする必要があります。 スレッドを中止しようとするとお勧めしません。
すでに受け入れられている回答に補足させていただきます。 私はこれに行き詰っていましたが、完了したイベントの処理については別のルートで行っていました。 awaitを実行するのではなく、私はタスクに完了ハンドラを追加します。
Comments.AsAsyncAction().Completed += new AsyncActionCompletedHandler(CommentLoadComplete);
イベントハンドラが以下のようなところ
private void CommentLoadComplete(IAsyncAction sender, AsyncStatus status )
{
if (status == AsyncStatus.Canceled)
{
return;
}
CommentsItemsControl.ItemsSource = Comments.Result;
CommentScrollViewer.ScrollToVerticalOffset(0);
CommentScrollViewer.Visibility = Visibility.Visible;
CommentProgressRing.Visibility = Visibility.Collapsed;
}
このルートでは、タスクがキャンセルされたときにイベントハンドラをトリガーするだけで、そこでキャンセルされたかどうかを確認することができ、すべての処理はすでにあなたのために行われます。