Czy prawidłowy sposób anulowania tokenu anulowania jest używany w zadaniu?


10

Mam kod, który tworzy token anulowania

public partial class CardsTabViewModel : BaseViewModel
{
   public CancellationTokenSource cts;

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

Kod, który go używa:

await GetCards(cts.Token);


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

i kod, który później anuluje ten Token Anulowania, jeśli użytkownik odejdzie od ekranu, na którym działa powyższy kod:

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

Jeśli chodzi o anulowanie, czy jest to właściwy sposób anulowania tokena, gdy jest on używany w zadaniu?

W szczególności sprawdziłem to pytanie:

Użycie właściwości IsCancellationRequested?

i sprawia, że ​​myślę, że nie robię anulowania we właściwy sposób, a może w sposób, który może powodować wyjątek.

Również w tym przypadku po anulowaniu powinienem zrobić cts.Dispose ()?


Zwykle użyj metody Anuluj, aby przekazać żądanie anulowania, a następnie użyj metody Dispose, aby zwolnić pamięć. Możesz sprawdzić próbkę w linku. docs.microsoft.com/en-us/dotnet/api/…
Wendy Zang - MSFT

Odpowiedzi:


2

CancellationTokenSource.Cancel() jest prawidłowym sposobem na rozpoczęcie anulowania.

Sondowanie ct.IsCancellationRequestedpozwala uniknąć rzucania OperationCanceledException. Ponieważ jego odpytywanie wymaga wykonania iteracji pętli, aby mogła odpowiedzieć na żądanie anulowania.

Jeśli GetViewablePhrases()i CheckAvailability()można je zmodyfikować, aby zaakceptować CancellationToken, może to przyspieszyć anulowanie, kosztem OperationCanceledExceptionrzucenia.

„powinienem robić cts.Dispose ()?” nie jest takie proste ...

„Zawsze usuwaj IDisposables JAK NAJSZYBCIEJ”

Jest bardziej wytyczną niż zasadą. Tasksam jest jednorazowy, ale prawie nigdy nie jest umieszczony bezpośrednio w kodzie.

Zdarzają się przypadki (gdy WaitHandleużywane są procedury obsługi wywołania zwrotnego lub anulowania), w których usunięcie ctsuwolni zasób / usunie katalog główny GC, który w innym przypadku zostałby uwolniony tylko przez Finalizator. Nie dotyczą one obecnego kodu, ale mogą w przyszłości.

Dodanie wywołania Disposepo anulowaniu zagwarantuje, że zasoby te zostaną niezwłocznie zwolnione w przyszłych wersjach kodu.

Musisz jednak poczekać na zakończenie działania kodu ctsprzed wywołaniem funkcji dispose lub zmodyfikować kod, aby poradził sobie z ObjectDisposedExceptionużyciem cts(lub jego tokena) po usunięciu.


„podłącz OnDisappearing, aby pozbyć się cts” Wydaje się to bardzo złym pomysłem, ponieważ wciąż jest używany w innym zadaniu. W szczególności, jeśli ktoś później zmieni projekt (zmodyfikuj podzadania, aby zaakceptować CancellationTokenparametr), możesz się pozbyć, WaitHandlegdy inny wątek aktywnie na niego czeka :(
Ben Voigt

1
W szczególności, ponieważ twierdziłeś, że „anuluj wykonuje to samo czyszczenie co usuwanie”, nie ma sensu dzwonić Disposez OnDisappearing.
Ben Voigt,

Ups, tęskniłem za tym, że kod w odpowiedzi już wywołuje Cancel...
Peter Wishart

Usunąłem roszczenie dotyczące anulowania robienia tego samego czyszczenia (o czym przeczytałem gdzie indziej), o ile mogę stwierdzić, że jedyne czyszczenie Cancelto wewnętrzny zegar (jeśli jest używany).
Peter Wishart,

3

Ogólnie widzę uczciwe użycie Anuluj token w twoim kodzie, ale zgodnie ze Wzorem Asynchronicznym Zadania twój kod może nie zostać natychmiast anulowany.

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

Aby odpowiedzieć od razu, kod blokujący również powinien zostać anulowany

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

Od Ciebie należy usunąć, jeśli w przerwanym kodzie zarezerwowano wiele zasobów pamięci, powinieneś to zrobić.


1
I rzeczywiście, dotyczyłoby to również połączenia z GetViewablePhrases - idealnie byłoby to również połączenie asynchroniczne i jako opcja przyjmowałby token anulowania.
Paddy

1

Polecam zajrzeć na jedną z klas .net, aby w pełni zrozumieć, jak obsługiwać metody oczekiwania za pomocą CanncelationToken, wybrałem 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;
    }

Możesz również wyświetlić całą klasę tutaj, https://referencesource.microsoft.com/#mscorlib/system/threading/SemaphoreSlim.cs,6095d9030263f169

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.