Чи правильний спосіб скасувати маркер скасування, який використовується у завданні?


10

У мене є код, який створює маркер скасування

public partial class CardsTabViewModel : BaseViewModel
{
   public CancellationTokenSource cts;

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

Код, який його використовує:

await GetCards(cts.Token);


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

і код, який пізніше скасовує цей маркер скасування, якщо користувач віддаляється від екрана, де працює вказаний вище код:

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

Що стосується скасування, чи це правильний спосіб скасувати маркер, коли він використовується у завданні?

Зокрема, я перевірив це питання:

Використання властивості IsCancellationRequeested?

і це змушує мене думати, що я не роблю скасування правильним способом або, можливо, таким чином, що може спричинити виняток.

Також у цьому випадку після скасування я повинен робити cts.Dispose ()?


Зазвичай використовуйте метод Скасувати, щоб надіслати запит на скасування, а потім скористайтеся методом Dispose для звільнення пам'яті. Ви можете перевірити зразок за посиланням. docs.microsoft.com/en-us/dotnet/api/…
Венді Занг - MSFT

Відповіді:


2

CancellationTokenSource.Cancel() є дійсним способом розпочати скасування.

Опитування ct.IsCancellationRequestedуникає метання OperationCanceledException. Оскільки його опитування вимагає завершення циклу, перш ніж він відповість на запит про скасування.

Якщо GetViewablePhrases()і CheckAvailability()можна змінити, щоб прийняти CancellationToken, це може зробити скасування швидшим, щоб відповісти, ціною OperationCanceledExceptionкинутого.

"я повинен робити cts.isispose ()?" не так просто ...

"Завжди розпоряджайтеся ідентифікаційними картками як можна швидше"

Це більше керівництво, ніж правило. Taskсама по собі є одноразовою, але навряд чи ніколи безпосередньо розміщується в коді.

Є випадки (коли використовуються WaitHandleабо скасовують обробники зворотних викликів), коли розпорядження ctsзвільнить ресурс / видалить GC-корінь, який інакше буде звільнений лише Finalizer. Вони не застосовуються до вашого коду, як він є, але можуть у майбутньому.

Додавання виклику Disposeпісля скасування гарантувало б негайне звільнення цих ресурсів у майбутніх версіях коду.

Однак вам доведеться або зачекати, коли код ctsзакінчиться перед викликом розпоряджатися, або змінити код для вирішення з ObjectDisposedExceptionвикористання cts(або його маркера) після вилучення.


"підключити OnDisappearing для розпорядження cts" Здається, це дуже погана ідея, тому що вона все ще використовується всередині іншого завдання. Особливо, якщо хтось пізніше змінить дизайн (змінити підзадачі, щоб прийняти CancellationTokenпараметр), ви можете розпоряджатися тим WaitHandleчасом, поки на нього активно чекає інша нитка :(
Бен

1
Зокрема, оскільки ви заявляли, що "скасування виконує таку саму очистку, як і утилізація", дзвонити Disposeз нього було б безглуздо OnDisappearing.
Ben Voigt

Ну, я пропустив, що код у відповіді вже дзвонить Cancel...
Пітер

Я видалив претензію на скасування того ж очищення (про який я читав в іншому місці), наскільки я можу сказати, єдиний спосіб очищення Cancel- це внутрішній таймер (якщо він використовується).
Пітер

3

Взагалі я бачу у вашому коді справедливе використання кнопки «Скасувати токен», але відповідно до шаблону асинхронної задачі ваш код може не скасовуватися негайно.

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

Для негайного реагування код блокування також повинен бути скасований

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

Ви повинні розпоряджатися, якщо в перерваному коді зарезервовано багато ресурсів пам'яті, вам слід це зробити.


1
Дійсно, це також стосуватиметься виклику GetViewablePhrases - в ідеалі це також буде виклик асинхронізації, і в якості опції приймається маркер скасування.
Падді

1

Я б рекомендував вам поглянути на один з класів .net, щоб повністю зрозуміти, як поводитися з методами очікування з CanncelationToken, я підібрав 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;
    }

Ви також можете переглянути весь клас тут, https://referencesource.microsoft.com/#mscorlib/system/threading/SemaphoreSlim.cs,6095d9030263f169

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.