web-dev-qa-db-fra.com

Comment annuler une tâche en attente?

Je joue avec ces tâches Windows 8 WinRT et j'essaie d'annuler une tâche à l'aide de la méthode ci-dessous, et cela fonctionne à un moment donné. La méthode CancelNotification DOIT être appelée, ce qui laisse penser que la tâche a été annulée, mais en arrière-plan, la tâche continue de s'exécuter, puis, une fois terminée, son statut est toujours terminé et jamais annulé. Y a-t-il un moyen d'arrêter complètement la tâche quand elle est annulée?

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()
{
}
143
Carlo

Lisez sur Annulation (introduit dans .NET 4.0 et est resté pratiquement inchangé depuis) ​​et le Modèle asynchrone basé sur les tâches , qui fournit des instructions sur la manière d'utiliser CancellationToken avec async méthodes.

Pour résumer, vous passez un CancellationToken dans chaque méthode prenant en charge l'annulation, et cette méthode doit la vérifier périodiquement.

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;
}
212
Stephen Cleary

Ou, afin d'éviter de modifier slowFunc (par exemple, vous n'avez pas accès au code source):

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

Vous pouvez également utiliser les méthodes d’extension Nice à partir de https://github.com/StephenCleary/AsyncEx et l’avoir aussi simple que:

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

Un cas qui n'a pas été traité est la gestion de l'annulation dans une méthode asynchrone. Prenons, par exemple, un cas simple dans lequel vous devez télécharger des données sur un service, le faire calculer, puis renvoyer des résultats.

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

Si vous souhaitez prendre en charge l'annulation, le moyen le plus simple consiste à transmettre un jeton et à vérifier s'il a été annulé entre chaque appel de méthode asynchrone (ou à l'aide de ContinueWith). Si les appels sont très longs, vous pouvez attendre un moment avant d’annuler. J'ai créé une petite méthode d'assistance pour échouer dès l'annulation.

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

Donc, pour l'utiliser, ajoutez simplement .WaitOrCancel(token) à tout appel asynchrone:

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

Notez que cela n'arrêtera pas la tâche que vous attendiez et continuera à s'exécuter. Vous devrez utiliser un mécanisme différent pour l'arrêter, tel que l'appel CancelAsync dans l'exemple ou, mieux encore, transmettre le même CancellationToken à la Task pour qu'il puisse gérer l'annulation éventuellement. Essayer d'abandonner le fil non recommandé .

9
kjbartel

Je veux juste ajouter à la réponse déjà acceptée. J'étais coincé là-dessus, mais je prenais un chemin différent pour gérer l'événement complet. Plutôt que d’attendre, j’ajoute un gestionnaire terminé à la tâche.

Comments.AsAsyncAction().Completed += new AsyncActionCompletedHandler(CommentLoadComplete);

Où le gestionnaire d'événement ressemble à ceci

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

Avec cette route, tout le traitement est déjà fait pour vous. Lorsque la tâche est annulée, elle déclenche simplement le gestionnaire d'événements et vous pouvez voir si elle a été annulée.

6
Smeegs