La manière correcte d'annuler un jeton d'annulation est-elle utilisée dans une tâche?

10

J'ai un code qui crée un jeton d'annulation

public partial class CardsTabViewModel : BaseViewModel
{
   public CancellationTokenSource cts;

public async Task OnAppearing()
{
   cts = new CancellationTokenSource(); // << runs as part of OnAppearing()

Code qui l'utilise:

await GetCards(cts.Token);


public async Task GetCards(CancellationToken ct)
{
    while (!ct.IsCancellationRequested)
    {
        App.viewablePhrases = App.DB.GetViewablePhrases(Settings.Mode, Settings.Pts);
        await CheckAvailability();
    }
}

et le code qui annule ultérieurement ce jeton d'annulation si l'utilisateur s'éloigne de l'écran sur lequel le code ci-dessus s'exécute:

public void OnDisappearing()
{
   cts.Cancel();

En ce qui concerne l'annulation, est-ce la bonne façon d'annuler le jeton lorsqu'il est utilisé dans une tâche?

En particulier, j'ai vérifié cette question:

Utilisation de la propriété IsCancellationRequested?

et cela me fait penser que je ne fais pas l'annulation de la bonne façon ou peut-être d'une manière qui peut provoquer une exception.

De plus, dans ce cas après avoir annulé, dois-je faire un cts.Dispose ()?

Alan2
la source
Normalement, utilisez la méthode Cancel pour communiquer une demande d'annulation, puis utilisez la méthode Dispose pour libérer la mémoire. Vous pouvez vérifier l'exemple de lien. docs.microsoft.com/en-us/dotnet/api/…
Wendy Zang - MSFT

Réponses:

2

CancellationTokenSource.Cancel() est un moyen valide pour commencer l'annulation.

Le vote ct.IsCancellationRequestedévite de lancer OperationCanceledException. Du fait de son interrogation, il nécessite une itération de la boucle pour se terminer avant de répondre à la demande d'annulation.

Si GetViewablePhrases()et CheckAvailability()peut être modifié pour accepter un CancellationToken, cela peut accélérer l'annulation de la réponse, au prix d'avoirOperationCanceledException jeté.

"devrais-je faire un cts.Dispose ()?" n'est pas si simple ...

"Toujours disposer des IDisposables dès que possible"

Est plus une ligne directrice qu'une règle. Tasklui-même est jetable, mais presque jamais directement disposé dans le code.

Il y a des cas (lorsque WaitHandleou des gestionnaires de rappel d'annulation sont utilisés) où la suppression ctslibérerait une ressource / supprimerait une racine GC qui ne serait autrement libérée que par un Finalizer. Ceux-ci ne s'appliquent pas à votre code tel qu'il est, mais pourraient le devenir à l'avenir.

L'ajout d'un appel à Disposeaprès l'annulation garantirait que ces ressources sont libérées rapidement dans les futures versions du code.

Cependant, vous devez soit attendre la fin du code qui utilise ctsavant d'appeler dispose, soit modifier le code à gérer à ObjectDisposedExceptionpartir de l'utilisation de cts(ou de son jeton) après la suppression.

Peter Wishart
la source
"branchez OnDisappearing pour éliminer les cts" Cela semble être une très mauvaise idée, car il est toujours utilisé dans une autre tâche. En particulier, si quelqu'un change la conception plus tard (modifiez les sous-tâches pour accepter un CancellationTokenparamètre), vous pourriez être WaitHandleen train de supprimer le pendant qu'un autre thread l'attend activement :(
Ben Voigt
1
En particulier, étant donné que vous avez affirmé que "annuler effectue le même nettoyage que disposer", il serait inutile d'appeler Disposedepuis OnDisappearing.
Ben Voigt
Oups, j'ai raté que le code dans la réponse appelle déjà Cancel...
Peter Wishart
J'ai supprimé l'affirmation selon laquelle annuler le même nettoyage (que j'avais lu ailleurs), autant que je sache, le seul nettoyage Cancelest le minuteur interne (s'il est utilisé).
Peter Wishart du
3

En général, je vois une utilisation équitable du jeton d'annulation dans votre code, mais selon le modèle de tâche asynchrone, votre code pourrait ne pas annuler immédiatement.

while (!ct.IsCancellationRequested)
{
   App.viewablePhrases = App.DB.GetViewablePhrases(Settings.Mode, Settings.Pts);
   await CheckAvailability();   //Your Code could be blocked here, unable to cancel
}

Pour répondre immédiatement, le code de blocage doit également être annulé

await CheckAvailability(ct);   //Your blocking code in the loop also should be stoped

C'est à vous si vous devez vous débarrasser, s'il y a beaucoup de ressources mémoire réservées dans le code interrompu, vous devez le faire.

Fidel Orozco
la source
1
Et en effet, cela s'appliquerait également à l'appel à GetViewablePhrases - idéalement, il s'agirait également d'un appel asynchrone et accepterait un jeton d'annulation en option.
Paddy
1

Je vous recommanderais de jeter un oeil sur l'une des classes .net pour bien comprendre comment gérer les méthodes d'attente avec CanncelationToken, j'ai choisi SeamaphoreSlim.cs

    public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
    {
        CheckDispose();

        // Validate input
        if (millisecondsTimeout < -1)
        {
            throw new ArgumentOutOfRangeException(
                "totalMilliSeconds", millisecondsTimeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong"));
        }

        cancellationToken.ThrowIfCancellationRequested();

        uint startTime = 0;
        if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout > 0)
        {
            startTime = TimeoutHelper.GetTime();
        }

        bool waitSuccessful = false;
        Task<bool> asyncWaitTask = null;
        bool lockTaken = false;

        //Register for cancellation outside of the main lock.
        //NOTE: Register/deregister inside the lock can deadlock as different lock acquisition orders could
        //      occur for (1)this.m_lockObj and (2)cts.internalLock
        CancellationTokenRegistration cancellationTokenRegistration = cancellationToken.InternalRegisterWithoutEC(s_cancellationTokenCanceledEventHandler, this);
        try
        {
            // Perf: first spin wait for the count to be positive, but only up to the first planned yield.
            //       This additional amount of spinwaiting in addition
            //       to Monitor.Enter()’s spinwaiting has shown measurable perf gains in test scenarios.
            //
            SpinWait spin = new SpinWait();
            while (m_currentCount == 0 && !spin.NextSpinWillYield)
            {
                spin.SpinOnce();
            }
            // entering the lock and incrementing waiters must not suffer a thread-abort, else we cannot
            // clean up m_waitCount correctly, which may lead to deadlock due to non-woken waiters.
            try { }
            finally
            {
                Monitor.Enter(m_lockObj, ref lockTaken);
                if (lockTaken)
                {
                    m_waitCount++;
                }
            }

            // If there are any async waiters, for fairness we'll get in line behind
            // then by translating our synchronous wait into an asynchronous one that we 
            // then block on (once we've released the lock).
            if (m_asyncHead != null)
            {
                Contract.Assert(m_asyncTail != null, "tail should not be null if head isn't");
                asyncWaitTask = WaitAsync(millisecondsTimeout, cancellationToken);
            }
                // There are no async waiters, so we can proceed with normal synchronous waiting.
            else
            {
                // If the count > 0 we are good to move on.
                // If not, then wait if we were given allowed some wait duration

                OperationCanceledException oce = null;

                if (m_currentCount == 0)
                {
                    if (millisecondsTimeout == 0)
                    {
                        return false;
                    }

                    // Prepare for the main wait...
                    // wait until the count become greater than zero or the timeout is expired
                    try
                    {
                        waitSuccessful = WaitUntilCountOrTimeout(millisecondsTimeout, startTime, cancellationToken);
                    }
                    catch (OperationCanceledException e) { oce = e; }
                }

                // Now try to acquire.  We prioritize acquisition over cancellation/timeout so that we don't
                // lose any counts when there are asynchronous waiters in the mix.  Asynchronous waiters
                // defer to synchronous waiters in priority, which means that if it's possible an asynchronous
                // waiter didn't get released because a synchronous waiter was present, we need to ensure
                // that synchronous waiter succeeds so that they have a chance to release.
                Contract.Assert(!waitSuccessful || m_currentCount > 0, 
                    "If the wait was successful, there should be count available.");
                if (m_currentCount > 0)
                {
                    waitSuccessful = true;
                    m_currentCount--;
                }
                else if (oce != null)
                {
                    throw oce;
                }

                // Exposing wait handle which is lazily initialized if needed
                if (m_waitHandle != null && m_currentCount == 0)
                {
                    m_waitHandle.Reset();
                }
            }
        }
        finally
        {
            // Release the lock
            if (lockTaken)
            {
                m_waitCount--;
                Monitor.Exit(m_lockObj);
            }

            // Unregister the cancellation callback.
            cancellationTokenRegistration.Dispose();
        }

        // If we had to fall back to asynchronous waiting, block on it
        // here now that we've released the lock, and return its
        // result when available.  Otherwise, this was a synchronous
        // wait, and whether we successfully acquired the semaphore is
        // stored in waitSuccessful.

        return (asyncWaitTask != null) ? asyncWaitTask.GetAwaiter().GetResult() : waitSuccessful;
    }

Vous pouvez également afficher la classe entière ici, https://referencesource.microsoft.com/#mscorlib/system/threading/SemaphoreSlim.cs,6095d9030263f169

Muhab
la source