क्या किसी कार्य में उपयोग किए गए रद्दीकरण टोकन को रद्द करने का सही तरीका है?


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 () कर रहा हूं?


सामान्य रूप से, रद्द करने के लिए अनुरोध को संवाद करने के लिए रद्द करें पद्धति का उपयोग करें और फिर मेमोरी जारी करने के लिए डिस्पोज़ विधि का उपयोग करें। आप लिंक में नमूना देख सकते हैं। docs.microsoft.com/en-us/dotnet/api/…
वेंडी ज़ंग - MSFT

जवाबों:


2

CancellationTokenSource.Cancel() रद्दीकरण शुरू करने का एक वैध तरीका है।

मतदान टालने ct.IsCancellationRequestedसे बचता है OperationCanceledException। क्योंकि इसके मतदान, इसे रद्द करने के अनुरोध का जवाब देने से पहले इसे पूरा करने के लिए लूप की पुनरावृत्ति की आवश्यकता होती है।

तो GetViewablePhrases()और CheckAvailability()एक को स्वीकार करने के लिए संशोधित किया जा सकता है CancellationToken, इस रद्द तेजी से प्रतिक्रिया के लिए होने की कीमत पर कर सकते हैं,OperationCanceledException फेंक दिया।

"क्या मुझे एक cts.Dispose () करना चाहिए?" क्या वह सीधा नहीं है ...

"हमेशा ASAP के IDisposables का निपटान करें"

एक नियम से अधिक दिशानिर्देश है। Taskस्वयं डिस्पोजेबल है, फिर भी शायद ही कभी सीधे कोड में निपटाया गया हो।

ऐसे मामले हैं (जब WaitHandleया रद्द करने के लिए कॉलबैक हैंडलर का उपयोग किया जाता है) जहां निपटान ctsएक संसाधन को मुक्त करेगा / जीसी रूट को हटा देगा जो अन्यथा केवल एक अंतिम रूप से मुक्त हो जाएगा। यह आपके कोड पर लागू नहीं होता है क्योंकि यह खड़ा है लेकिन भविष्य में हो सकता है।

Disposeरद्द करने के बाद एक कॉल जोड़ना यह गारंटी देगा कि ये संसाधन कोड के भविष्य के संस्करणों में तुरंत मुक्त हो गए हैं।

हालाँकि, आपको या तो उस कोड का इंतजार करना होगा जो ctsनिपटान को कॉल करने से पहले समाप्त करने के लिए उपयोग करता है, या निपटान के बाद (या इसके टोकन) के ObjectDisposedExceptionउपयोग से निपटने के लिए कोड को संशोधित करता है cts


"हुक अप करने के लिए OnDisappearing cts को निपटाना" एक बहुत बुरा विचार लगता है, क्योंकि, यह अभी भी किसी अन्य कार्य के अंदर उपयोग में है। विशेष रूप से अगर कोई बाद में डिज़ाइन को बदलता है (एक CancellationTokenपैरामीटर को स्वीकार करने के लिए उपशीर्षक को संशोधित करें ), तो आप निपटारा कर सकते हैं WaitHandleजबकि दूसरा धागा सक्रिय रूप से इस पर इंतजार कर रहा है :(
बेन Voigt

1
विशेष रूप से, क्योंकि आप दावा किया है कि "प्रदर्शन निपटाने के रूप में ही सफाई अभी नहीं", इसे कहते हैं व्यर्थ होगा Disposeसे OnDisappearing
बेन वोइगट

वूप्स, मैंने याद किया कि उत्तर में कोड पहले से ही कहता है Cancel...
पीटर Wishart

एक ही सफाई (जो मैं कहीं और पढ़ता हूं) को रद्द करने के बारे में दावा हटा दिया है, जहां तक ​​मैं बता सकता हूं कि केवल सफाई ही 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 के लिए कॉल पर भी लागू होगा - आदर्श रूप में यह एक async कॉल होगा और एक विकल्प के रूप में रद्द टोकन में ले जाएगा।
धान

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.