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


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.