Il modo corretto di annullare un token di annullamento viene utilizzato in un'attività?


10

Ho un codice che crea un token di annullamento

public partial class CardsTabViewModel : BaseViewModel
{
   public CancellationTokenSource cts;

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

Codice che lo utilizza:

await GetCards(cts.Token);


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

e codice che successivamente annulla questo token di annullamento se l'utente si allontana dalla schermata in cui è in esecuzione il codice sopra:

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

Per quanto riguarda la cancellazione, è questo il modo corretto di annullare il token quando viene utilizzato in un'attività?

In particolare ho verificato questa domanda:

L'uso della proprietà IsCancellationRequested?

e mi sta facendo pensare che non sto facendo l'annullamento nel modo corretto o forse in un modo che può causare un'eccezione.

Inoltre, in questo caso dopo che ho annullato, dovrei fare un cts.Dispose ()?


Normalmente, utilizzare il metodo Annulla per comunicare una richiesta di annullamento, quindi utilizzare il metodo Dispose per liberare la memoria. È possibile controllare l'esempio nel collegamento. docs.microsoft.com/en-us/dotnet/api/…
Wendy Zang - MSFT

Risposte:


2

CancellationTokenSource.Cancel() è un modo valido per iniziare la cancellazione.

Il polling ct.IsCancellationRequestedevita di lanciare OperationCanceledException. A causa del suo polling, richiede il completamento di un'iterazione del ciclo prima che risponderà alla richiesta di annullamento.

Se GetViewablePhrases()e CheckAvailability()può essere modificato per accettare un CancellationToken, questo può rendere più veloce la cancellazione per rispondere, a costo di averlo OperationCanceledExceptionlanciato.

"dovrei fare un cts.Dispose ()?" non è così semplice ...

"Smaltisci sempre IDisposables AL PIÙ PRESTO"

È più una linea guida che una regola. Taskdi per sé è usa e getta, ma quasi mai direttamente nel codice.

Ci sono casi (quando WaitHandlevengono utilizzati i gestori di callback di annullamento o annullamento) in cui lo smaltimento ctslibera una risorsa / rimuove una radice GC che altrimenti verrebbe liberata solo da un Finalizzatore. Questi non si applicano al tuo codice così com'è, ma potrebbero in futuro.

L'aggiunta di una chiamata a Disposedopo l'annullamento garantirebbe che queste risorse vengano liberate prontamente nelle future versioni del codice.

Tuttavia, dovresti attendere il completamento del codice che utilizza ctsprima di chiamare dispose oppure modificare il codice per gestire l' ObjectDisposedExceptionutilizzo cts(o il relativo token) dopo lo smaltimento.


"agganciare OnDisappearing per eliminare i cts" Sembra una pessima idea, perché è ancora in uso all'interno di un'altra attività. Soprattutto se in seguito qualcuno cambia il design (modifica le attività secondarie per accettare un CancellationTokenparametro), potresti essere disposto WaitHandlementre un altro thread lo sta attivamente aspettando :(
Ben Voigt

1
In particolare, poiché hai affermato che "Annulla esegue la stessa pulizia di Dispose", sarebbe inutile chiamare Disposeda OnDisappearing.
Ben Voigt,

Spiacenti, mi mancava che il codice nella risposta già chiama Cancel...
Peter Wishart il

Ho cancellato la richiesta di annullamento facendo la stessa pulizia (che avrei letto altrove), per quanto ne so, l'unica pulizia che Cancelfa è il timer interno (se usato).
Peter Wishart,

3

In generale vedo un uso corretto di Annulla token nel tuo codice, ma secondo il modello Async attività il tuo codice potrebbe non essere cancellato immediatamente.

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

Per rispondere immediatamente, anche il codice di blocco deve essere cancellato

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

Spetta a te se devi disporre, se ci sono molte risorse di memoria riservate nel codice interrotto, dovresti farlo.


1
E in effetti ciò si applicherebbe anche alla chiamata a GetViewablePhrases - idealmente anche questa sarebbe una chiamata asincrona e richiederebbe un token di annullamento come opzione.
Paddy,

1

Ti consiglierei di dare un'occhiata a una delle classi .net per comprendere appieno come gestire i metodi di attesa con CanncelationToken, ho preso 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;
    }

Puoi anche visualizzare l'intera classe qui, https://referencesource.microsoft.com/#mscorlib/system/threading/SemaphoreSlim.cs,6095d9030263f169

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.