web-dev-qa-db-ja.com

待機中のタスクをキャンセルするには?

私はこれらの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()
{
}
141
Carlo

キャンセル (.NET 4.0で導入され、それ以降ほとんど変更されていない)および タスクベースの非同期パターン で、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;
}
208
Stephen Cleary

または、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 からNice拡張メソッドを使用して、次のようなシンプルな外観にすることもできます。

await Task.WhenAny(task, source.Token.AsTask());
30
sonatique

取り上げられていないケースの1つは、非同期メソッド内でキャンセルを処理する方法です。たとえば、サービスにデータをアップロードする必要がある簡単なケースを考えてみましょう。データを取得して何かを計算し、結果を返します。

public async Task<Results> ProcessDataAsync(MyData data)
{
    var client = await GetClientAsync();
    await client.UploadDataAsync(data);
    await client.CalculateAsync();
    return await client.GetResultsAsync();
}

キャンセルをサポートする場合、最も簡単な方法は、トークンを渡して、各非同期メソッド呼び出しの間に(または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;
    }
}

それを使用するには、非同期呼び出しに.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;
    }
}
8
kjbartel

すでに受け入れられている答えに追加したいだけです。私はこれに固執していましたが、完全なイベントを処理するために別のルートを行っていました。 awaitを実行するのではなく、completedハンドラーをタスクに追加します。

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;
}

このルートでは、すべての処理が既に行われています。タスクがキャンセルされると、イベントハンドラーがトリガーされるだけで、そこでキャンセルされたかどうかを確認できます。

6
Smeegs