Informations
Le sujet que vous avez demandé est indiqué ci-dessous. Toutefois, ce sujet ne figure pas dans la bibliothèque.

Annulation dans les threads managés

Le .NET Framework 4 introduit un nouveau modèle unifié pour l'annulation coopérative des opérations asynchrones ou synchrones à durée d'exécution longue. Ce modèle est basé sur un objet simplifié appelé jeton d'annulation. L'objet qui appelle une opération annulable, par exemple en créant un nouveau thread ou une nouvelle tâche, passe le jeton à l'opération. Cette opération peut, à son tour, passer des copies du jeton à d'autres opérations. L'objet qui a créé le jeton peut l'utiliser ultérieurement pour demander à l'opération d'arrêter son activité. Seul l'objet demandeur peut émettre la demande d'annulation, et chaque écouteur est chargé de remarquer la demande et d'y répondre en temps voulu. L'illustration suivante présente la relation entre la source d'un jeton et toutes les copies de son jeton.

CancellationTokenSource et CancellationTokens

Le nouveau modèle d'annulation simplifie la création d'applications et de bibliothèques compatibles avec l'annulation, et il prend en charge les fonctionnalités suivantes :

  • L'annulation est coopérative et n'est pas imposée à l'écouteur. L'écouteur détermine la façon de se terminer correctement en réponse à une demande d'annulation.

  • La demande est différente de l'écoute. Un objet qui appelle une opération annulable peut contrôler le moment où (le cas échéant) l'annulation est demandée.

  • L'objet demandeur émet la demande d'annulation à toutes les copies du jeton à l'aide d'un seul appel de méthode.

  • Un écouteur peut écouter simultanément plusieurs jetons en les joignant dans un jeton lié.

  • Le code utilisateur peut remarquer des demandes d'annulation provenant d'un code de bibliothèque et y répondre, et le code de bibliothèque peut remarquer des demandes d'annulation provenant d'un code utilisateur et y répondre.

  • Les écouteurs peuvent être avertis de demandes d'annulation par l'interrogation, l'inscription du rappel ou l'attente sur des handles d'attente.

La nouvelle infrastructure d'annulation est implémentée sous la forme d'un ensemble de types connexes, répertoriés dans le tableau suivant.

Nom de type

Description

CancellationTokenSource

Objet qui crée un jeton d'annulation, et émet également la demande d'annulation pour toutes les copies de ce jeton.

CancellationToken

Type valeur simplifié passé à un ou plusieurs écouteurs, généralement sous la forme d'un paramètre de méthode. Les écouteurs surveillent la valeur de la propriété IsCancellationRequested du jeton par l'interrogation, le rappel ou un handle d'attente.

OperationCanceledException

Les nouvelles surcharges de cette exception acceptent un CancellationToken comme paramètre d'entrée. Les écouteurs peuvent éventuellement lever cette exception pour vérifier la source de l'annulation et avertir d'autres utilisateurs qu'elle a répondu à une demande d'annulation.

Le modèle d'annulation est intégré .NET Framework dans plusieurs types. Le plus important est System.Threading.Tasks.Parallel, System.Threading.Tasks.Task, System.Threading.Tasks.Task<TResult> et System.Linq.ParallelEnumerable. Nous vous recommandons d'utiliser ce nouveau modèle d'annulation pour tout le nouveau code de bibliothèque et d'application.

Dans l'exemple suivant, l'objet demandeur crée un objet CancellationTokenSource, puis passe sa propriété Token à l'opération annulable. L'opération qui reçoit la demande surveille la valeur de la propriété IsCancellationRequested du jeton par l'interrogation. Lorsque la valeur devient true, l'écouteur peut s'arrêter de quelque manière appropriée que ce soit. Dans cet exemple, la méthode s'arrête simplement, ce qui, dans de nombreux cas, est la seule opération requise.

Remarque Remarque

L'exemple utilise la méthode QueueUserWorkItem pour montrer que la nouvelle infrastructure d'annulation est compatible avec les API héritées. Pour obtenir un exemple qui utilise le nouveau type System.Threading.Tasks.Task par défaut, consultez Comment : annuler une tâche et ses enfants.


static void CancelWithThreadPoolMiniSnippet()
{

    //Thread 1: The Requestor
    // Create the token source.
    CancellationTokenSource cts = new CancellationTokenSource();

    // Pass the token to the cancelable operation.
    ThreadPool.QueueUserWorkItem(new WaitCallback(DoSomeWork), cts.Token);

    // Request cancellation by setting a flag on the token.
    cts.Cancel();
}

//Thread 2: The Listener
static void DoSomeWork(object obj)
{
    CancellationToken token = (CancellationToken)obj;
    for (int i = 0; i < 100000; i++)
    {
        // Simulating work.
        Thread.SpinWait(5000000);

        if (token.IsCancellationRequested)
        {
            // Perform cleanup if necessary.
            //...
            // Terminate the operation.
            break;
        }
    }
}


Dans la nouvelle infrastructure d'annulation, l'annulation fait référence aux opérations, et non aux objets. La demande d'annulation signifie que l'opération doit s'arrêter dès que possible après l'exécution de tout nettoyage requis. Un jeton d'annulation doit faire référence à une « opération annulable » ; toutefois, cette opération puisse être implémentée dans votre programme. Une fois la valeur true affectée à la propriété IsCancellationRequested du jeton, il n'est pas possible de la réinitialiser avec la valeur false. Par conséquent, les jetons d'annulation ne peuvent pas être réutilisés après avoir été annulés.

Si vous avez besoin d'un mécanisme d'annulation d'objet, vous pouvez le baser sur le mécanisme d'annulation d'opération, comme indiqué dans l'exemple suivant.


CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;

// User defined Class with its own method for cancellation
var obj1 = new MyCancelableObject();
var obj2 = new MyCancelableObject();
var obj3 = new MyCancelableObject();

// Register the object's cancel method with the token's
// cancellation request.
token.Register(() => obj1.Cancel());
token.Register(() => obj2.Cancel());
token.Register(() => obj3.Cancel());

// Request cancellation on the token.
cts.Cancel();


Si un objet prend en charge plusieurs opérations annulables simultanées, passez un jeton séparé comme entrée à chaque opération annulable distincte. Ainsi, une opération peut être annulée sans affecter les autres.

L'implémenteur d'une opération annulable détermine, dans le délégué utilisateur, la façon de mettre fin à l'opération en réponse à une demande d'annulation. Dans de nombreux cas, le délégué utilisateur peut simplement exécuter tout le nettoyage requis, puis effectuer immédiatement un retour.

Toutefois, dans des cas plus complexes, il peut s'avérer nécessaire, pour le délégué utilisateur, d'indiquer au code de bibliothèque qu'une annulation a été effectuée. Dans ces cas-là, la façon correcte de terminer l'opération consiste, pour le délégué, à appeler ThrowIfCancellationRequested, ce qui entraînera la levée d'un OperationCanceledException. Dans le .NET Framework 4, les nouvelles surcharges de cette exception prennent un CancellationToken comme argument. Le code de bibliothèque peut intercepter cette exception sur le thread du délégué utilisateur, et examine le jeton de l'exception afin de déterminer si l'exception indique une annulation coopérative ou une autre situation exceptionnelle.

La classe Task gère OperationCanceledException de cette façon. Pour plus d'informations, consultez Annulation de tâches.

Dd997364.collapse_all(fr-fr,VS.110).gifÉcoute par l'interrogation

Pour les calculs à durée d'exécution longue qui s'exécutent en boucle ou sont parcourus de manière récursive, vous pouvez écouter une demande d'annulation en interrogeant régulièrement la valeur de la propriété CancellationToken.IsCancellationRequested. Si la valeur est true, la méthode doit procéder à un nettoyage et se terminer aussi rapidement que possible. La fréquence optimale de l'interrogation dépend du type d'application. Il incombe au développeur de déterminer la meilleure fréquence d'interrogation pour un programme donné. L'interrogation elle-même n'altère pas beaucoup les performances. L'exemple suivant présente une façon possible d'effectuer une interrogation.


static void NestedLoops(Rectangle rect, CancellationToken token)
{
    for (int x = 0; x < rect.columns && !token.IsCancellationRequested; x++)
    {
        for (int y = 0; y < rect.rows; y++)
        {
            // Simulating work.
            Thread.SpinWait(5000);
            Console.Write("{0},{1} ", x, y);
        }

        // Assume that we know that the inner loop is very fast.
        // Therefore, checking once per row is sufficient.
        if (token.IsCancellationRequested)
        {
            // Cleanup or undo here if necessary...
            Console.WriteLine("\r\nCancelling after row {0}.", x);
            Console.WriteLine("Press any key to exit.");
            // then...
            break;
            // ...or, if using Task:
            // token.ThrowIfCancellationRequested();
        }
    }
}


Pour un exemple plus complet, consultez Comment : écouter les demandes d'annulation par l'interrogation.

Dd997364.collapse_all(fr-fr,VS.110).gifÉcoute en inscrivant un rappel

Certaines opérations peuvent être bloquées de telle sorte qu'elles ne peuvent pas vérifier la valeur du jeton d'annulation en temps voulu. Dans ces cas-là, vous pouvez inscrire une méthode de rappel qui débloque la méthode lorsqu'une demande d'annulation est reçue.

La méthode Register retourne un objet CancellationTokenRegistration qui est spécialement utilisé à cette fin. L'exemple suivant montre comment utiliser la méthode Register pour annuler une requête Web asynchrone.


CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;            
WebClient wc = new WebClient();

// To request cancellation on the token
// will call CancelAsync on the WebClient.
token.Register(() => wc.CancelAsync());

Console.WriteLine("Starting request");
wc.DownloadStringAsync(new Uri("http://www.contoso.com"));


L'objet CancellationTokenRegistration gère la synchronisation des threads et vérifie que le rappel cessera de s'exécuter à un moment donné.

Pour vérifier la réactivité du système et éviter les interblocages, les indications suivantes doivent être suivies lors de l'enregistrement de rappels :

  • La méthode de rappel doit être rapide car elle est appelée de façon synchrone, par conséquent, l'appel à Cancel n'est pas retourné tant que le rappel n'est pas retourné.

  • Si vous appelez Dispose pendant l'exécution du rappel et détenez un verrou que le rappel attend, votre programme peut connaître un interblocage. Après le retour de Dispose, vous pouvez libérer toutes les ressources requises par le rappel.

  • Les rappels ne doivent pas exécuter de thread manuel ni utiliser SynchronizationContext. Si un rappel doit s'exécuter sur un thread particulier, utilisez le constructeur System.Threading.CancellationTokenRegistration qui vous permet de spécifier que le syncContext cible est le SynchronizationContext.Current actif. L'exécution de threads manuels dans un rappel peut provoquer un interblocage.

Pour un exemple plus complet, consultez Comment : enregistrer des rappels pour les demandes d'annulation.

Dd997364.collapse_all(fr-fr,VS.110).gifÉcoute à l'aide d'un handle d'attente

Lorsqu'une opération annulable peut se bloquer pendant qu'elle attend sur une primitive de synchronisation telle qu'un System.Threading.ManualResetEvent ou un System.Threading.Semaphore, vous pouvez utiliser la propriété CancellationToken.WaitHandle pour permettre à l'opération d'attendre à la fois sur l'événement et sur la demande d'annulation. Le handle d'attente du jeton d'annulation sera signalé en réponse à une demande d'annulation, et la méthode peut utiliser la valeur de retour de la méthode WaitAny pour déterminer si c'est le jeton d'annulation qui est à l'origine de la signalisation. L'opération peut alors simplement s'arrêter, ou lever un OperationCanceledException, selon le cas.


// Wait on the event if it is not signaled.
int eventThatSignaledIndex =
    WaitHandle.WaitAny(new WaitHandle[] { mre, token.WaitHandle },
                        new TimeSpan(0, 0, 20));


Dans le nouveau code qui cible le .NET Framework 4, System.Threading.ManualResetEventSlim et System.Threading.SemaphoreSlim prennent tous les deux en charge la nouvelle infrastructure d'annulation dans leurs méthodes Wait. Vous pouvez passer CancellationToken à la méthode ; lorsque l'annulation est demandée, l'événement est alors réactivé et lève un OperationCanceledException.


try
{
    // mres is a ManualResetEventSlim
    mres.Wait(token);
}
catch (OperationCanceledException)
{
    // Throw immediately to be responsive. The
    // alternative is to do one more item of work,
    // and throw on next iteration, because 
    // IsCancellationRequested will be true.
    Console.WriteLine("The wait operation was canceled.");
    throw;
}

Console.Write("Working...");
// Simulating work.
Thread.SpinWait(500000);


Pour un exemple plus complet, consultez Comment : écouter les demandes d'annulation avec des handles d'attente.

Dd997364.collapse_all(fr-fr,VS.110).gifÉcoute simultanée de plusieurs jetons

Dans certains cas, un écouteur peut avoir à écouter simultanément plusieurs jetons d'annulation. Par exemple, une opération annulable peut avoir à surveiller un jeton d'annulation interne en plus d'un jeton passé de manière externe comme argument à un paramètre de méthode. Pour cela, créez une source de jeton lié qui peut joindre deux jetons, ou plus, dans un même jeton, comme indiqué dans l'exemple suivant.


public void DoWork(CancellationToken externalToken)
{
    // Create a new token that combines the internal and external tokens.
    this.internalToken = internalTokenSource.Token;
    this.externalToken = externalToken;

    using (CancellationTokenSource linkedCts =
            CancellationTokenSource.CreateLinkedTokenSource(internalToken, externalToken))
    {
        try
        {
            DoWorkInternal(linkedCts.Token);
        }
        catch (OperationCanceledException)
        {
            if (internalToken.IsCancellationRequested)
            {
                Console.WriteLine("Operation timed out.");
            }
            else if (externalToken.IsCancellationRequested)
            {
                Console.WriteLine("Cancelling per user request.");
                externalToken.ThrowIfCancellationRequested();
            }
        }
    }
}


Notez que vous devez appeler Dispose sur la source du jeton lié lorsque vous n'en avez plus besoin. Pour un exemple plus complet, consultez Comment : écouter plusieurs demandes d'annulation.

L'infrastructure d'annulation unifiée permet à du code de bibliothèque d'annuler du code utilisateur, et à du code utilisateur d'annuler du code de bibliothèque de façon coopérative. Une coopération harmonieuse dépend du respect, par les deux parties, des indications suivantes :

  • Si le code de bibliothèque fournit des opérations annulables, il doit également fournir des méthodes publiques qui acceptent un jeton d'annulation externe afin que le code utilisateur puisse demander l'annulation.

  • Si le code de bibliothèque effectue un appel dans le code utilisateur, le code de bibliothèque doit interpréter un OperationCanceledException(externalToken) comme une annulation coopérative, et pas nécessairement comme une exception d'échec.

  • Les délégués utilisateur doivent tenter de répondre en temps voulu aux demandes d'annulation du code de bibliothèque.

System.Threading.Tasks.Task et System.Linq.ParallelEnumerable sont des exemples de classes qui suivent ces indications. Pour plus d'informations, consultez Annulation de tâches et Comment : annuler une requête PLINQ.

Ajouts de la communauté

Afficher:
© 2014 Microsoft