187 votes

Comment annuler une tâche en attente ?

Je joue avec ces tâches WinRT de Windows 8, et j'essaie d'annuler une tâche en utilisant la méthode ci-dessous, et cela fonctionne jusqu'à un certain point. La méthode CancelNotification est effectivement 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, et une fois qu'elle est terminée, l'état de la tâche est toujours terminé et jamais annulé. Existe-t-il un moyen d'arrêter complètement la tâche lorsqu'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()
{
}

0 votes

Je viens de trouver cet article qui m'a aidé à comprendre les différentes manières d'annuler.

273voto

Stephen Cleary Points 91731

Lire la suite Annulation (qui a été introduite dans .NET 4.0 et est largement inchangée depuis) et l'option Modèle asynchrone basé sur les tâches qui fournit des directives sur la manière d'utiliser CancellationToken avec async méthodes.

Pour résumer, vous passez un CancellationToken dans chaque méthode qui supporte l'annulation, et cette méthode doit le 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;
}

2 votes

Wow, super info ! Cela a parfaitement fonctionné, maintenant je dois trouver comment gérer l'exception dans la méthode asynchrone. Merci ! Je vais lire ce que tu as suggéré.

4 votes

Hé mec, y a-t-il un moyen de le faire si je n'ai pas accès à la méthode lente ? Par exemple, supposons que slowFunc soit dans une boîte noire et que tu n'aies accès qu'à l'appel de la méthode, mais que tu ne puisses rien modifier à l'intérieur ?

12 votes

Non. La plupart des méthodes synchrones à longue durée d'exécution ont un peu de un moyen de les annuler - parfois en fermant une ressource sous-jacente ou en appelant une autre méthode. CancellationToken possède tous les crochets nécessaires pour interopérer avec des systèmes d'annulation personnalisés, mais rien ne peut annuler une méthode non annulable.

47voto

sonatique Points 1282

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

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 de belles méthodes d'extension de https://github.com/StephenCleary/AsyncEx et le faire paraître aussi simple que :

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

24voto

kjbartel Points 453

Un cas qui n'a pas été abordé est celui de la gestion de l'annulation à l'intérieur d'une méthode asynchrone. Prenons par exemple un cas simple où vous devez télécharger des données vers un service pour qu'il calcule quelque chose et renvoie 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 serait de passer un jeton et de vérifier s'il a été annulé entre chaque appel de méthode asynchrone (ou en utilisant ContinueWith). S'il s'agit d'appels très longs, vous risquez d'attendre un certain temps avant de pouvoir annuler. J'ai créé une petite méthode d'aide pour échouer dès qu'elle est annulée.

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

Pour l'utiliser, il suffit donc d'ajouter .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 qu'elle continuera à fonctionner. Vous devrez utiliser un mécanisme différent pour l'arrêter, comme la fonction CancelAsync dans l'exemple, ou mieux encore, passer dans la même CancellationToken à la Task pour qu'il puisse éventuellement gérer l'annulation. Essayer d'interrompre le fil n'est pas recommandé .

6voto

Smeegs Points 5616

Je veux juste ajouter à la réponse déjà acceptée. J'étais bloqué sur ce point, mais je suivais une voie différente pour gérer l'événement complet. Plutôt que de lancer await, j'ajoute un gestionnaire de l'événement complete à 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, toute la gestion est déjà faite pour vous, lorsque la tâche est annulée, il suffit de déclencher le gestionnaire d'événements et vous pouvez voir si elle a été annulée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X