작업에 사용 된 취소 토큰을 취소하는 올바른 방법입니까?


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();

취소와 관련하여, 이것이 작업에서 사용될 때 토큰을 취소하는 올바른 방법입니까?

특히이 질문을 확인했습니다.

IsCancellationRequested 속성을 사용합니까?

그리고 나는 올바른 방법으로 또는 아마도 예외를 일으킬 수있는 방법으로 취소를하지 않는다고 생각하게합니다.

또한이 경우 취소 한 후 cts.Dispose ()를 수행해야합니까?


일반적으로 Cancel 메서드를 사용하여 취소 요청을 전달한 다음 Dispose 메서드를 사용하여 메모리를 해제합니다. 링크에서 샘플을 확인할 수 있습니다. docs.microsoft.com/ko-kr/dotnet/api/…
— Wendy Zang-MSFT 2009

답변:


2

CancellationTokenSource.Cancel() 취소를 시작하는 올바른 방법입니다.

폴링 ct.IsCancellationRequested은 던지기를 피 OperationCanceledException합니다. 폴링이므로 취소 요청에 응답하기 전에 루프 반복을 완료해야합니다.

경우 GetViewablePhrases()와 CheckAvailability()a를 수정할 수 있습니다 CancellationToken, 이것은 한의 비용으로, 응답에 빠르게 취소 할 수 있습니다 OperationCanceledException발생합니다.

"cts.Dispose ()를 수행해야합니까?" 그렇게 간단하지 않습니다 ...

"항상 IDisposables를 최대한 빨리 폐기하십시오"

규칙보다 더 많은 지침이 있습니다. Task그 자체는 일회용이지만 코드에 직접 배치되는 경우는 거의 없습니다.

WaitHandle처분 cts하면 리소스를 해제하거나 GC 루트를 제거하고 그렇지 않으면 Finalizer 만 해제하는 경우가 있습니다 ( 콜백 핸들러가 취소되거나 취소되는 경우) . 이것들은 여러분의 코드에는 적용되지 않지만 앞으로는 적용되지 않을 것입니다.

Dispose취소 후 호출을 추가하면 이후 버전의 코드에서 이러한 리소스가 즉시 해제됩니다.

그러나 ctsdispose를 호출하기 전에 완료 하는 데 사용되는 코드를 기다리 거나 폐기 후 (또는 토큰) ObjectDisposedException사용 을 처리하도록 코드를 수정해야합니다 cts.


"CTS를 처분하기 위해 OnDisappearing을 연결하십시오"다른 작업에서 여전히 사용되고 있기 때문에 매우 나쁜 생각 인 것 같습니다. 특히 누군가 나중에 디자인을 변경하면 (하위 작업을 수정하여 CancellationToken매개 변수 를 수락하는 경우 ) WaitHandle다른 스레드가 적극적으로 대기 하는 동안 처리 할 수 ​​있습니다. (
— Ben Voigt

1
당신이 "처분으로 수행에게 같은 정리를 취소"는 주장을했기 때문에 특히, 전화를 무의미 Dispose에서 OnDisappearing.
— Ben Voigt

으악, 나는 대답의 코드가 이미 전화하는 것을 그리워했다 Cancel.
— Peter Wishart

유일한 정리 Cancel가 내부 타이머 (사용 된 경우) 만 말할 수있는 한 동일한 정리 (다른 곳에서 읽은 것)를 취소하는 것에 대한 주장을 삭제했습니다 .
— Peter Wishart

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에 대한 호출에도 적용됩니다. 이상적으로는 비동기 호출이며 취소 토큰을 옵션으로 사용합니다.
— Paddy

1

CanncelationToken으로 대기 메소드를 처리하는 방법을 완전히 이해하려면 .net 클래스 중 하나를 살펴 보는 것이 좋습니다. 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 에서 전체 클래스를 볼 수도 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.