Görevde kullanılan iptal belirtecini iptal etmenin doğru yolu var mı?


10

Bir iptal belirteci oluşturan kod var

public partial class CardsTabViewModel : BaseViewModel
{
   public CancellationTokenSource cts;

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

Kullanan kod:

await GetCards(cts.Token);


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

ve kullanıcı yukarıdaki kodun çalıştığı ekrandan uzaklaşırsa bu İptal Simgesini daha sonra iptal eden kod:

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

İptal ile ilgili olarak, bir Görevde kullanıldığında jetonu iptal etmenin doğru yolu var mı?

Özellikle bu soruyu kontrol ettim:

IsCancellationRequested özelliğinin kullanımı?

ve iptal işlemini doğru şekilde yapmadığımı ya da belki de bir istisnaya neden olacak şekilde yapmadığımı düşündürüyor.

Ayrıca, bu durumda iptal ettikten sonra bir cts.Dispose () yapmalıyım?


Normalde, iptal isteğini iletmek için İptal yöntemini kullanın ve ardından belleği serbest bırakmak için Dispose yöntemini kullanın. Bağlantıdaki örneği kontrol edebilirsiniz. docs.microsoft.com/tr-tr/dotnet/api/…
Wendy Zang - MSFT

Yanıtlar:


2

CancellationTokenSource.Cancel() iptal işlemine başlamak için geçerli bir yoldur.

Yoklama ct.IsCancellationRequestedatmayı önler OperationCanceledException. Yoklaması nedeniyle, iptal isteğine yanıt vermeden önce döngünün tamamlanması gerekir.

Eğer GetViewablePhrases()ve CheckAvailability()bir kabul için değiştirilebilir CancellationToken, bu ettikten pahasına, yanıt verdiklerini daha hızlı iptalini yapabilir OperationCanceledExceptionatılmış.

"Bir cts.Dispose () yapmalı mıyım?" o kadar basit değil ...

"Tek kullanımlık IDAP'ları her zaman en kısa sürede atın"

Kuraldan ziyade bir kılavuzdur. Taskkendisi tek kullanımlıktır, ancak neredeyse hiç doğrudan kodda yer almaz.

WaitHandleİmha işleminin ctsbir kaynağı serbest bırakacağı / yalnızca bir Sonlandırıcı tarafından serbest bırakılacak bir GC kökünü kaldıracağı durumlar ( iptal veya geri çağırma işleyicileri kullanıldığında) vardır . Bunlar kodunuz için geçerli olduğu için geçerli değildir, ancak gelecekte olabilir.

Disposeİptal ettikten sonra bir çağrı eklemek , bu kaynakların kodun gelecekteki sürümlerinde derhal serbest bırakılmasını garanti eder.

Ancak, ctsimha çağrılmadan önce kullanılan kodun bitmesini beklemeniz veya imha işleminden sonra (veya belirtecinin) ObjectDisposedExceptionkullanımıyla ilgili kodu değiştirmeniz gerekir cts.


"OnDisappting cts bertaraf kanca" Çok kötü bir fikir gibi görünüyor, çünkü, hala başka bir görev içinde kullanılıyor. Özellikle bir kişi daha sonra tasarımı değiştirirse (bir CancellationTokenparametreyi kabul etmek için alt görevleri değiştirin ), WaitHandlebaşka bir iş parçacığı aktif olarak beklerken onu atabilirsin :(
Ben Voigt

1
Eğer "Revize olarak gerçekleştirdiği aynı temizleme iptal" iddiasını yapılan çünkü Özellikle, çağrı anlamsız olacağını Disposedan OnDisappearing.
Ben Voigt

Hata! Cevaptaki kodun zaten aradığını kaçırdım Cancel...
Peter Wishart

Tek temizleme Cancel(eğer kullanılıyorsa) söyleyebildiğim kadarıyla aynı temizliği (başka bir yerde okudum) yapmayı iptal etme iddiasını sildim .
Peter Wishart

3

Genel olarak, Kodunuzda İptal Simgesinin adil bir şekilde kullanıldığını görüyorum, ancak Görev Eşzamansız Deseni'ne göre kodunuz hemen iptal edilemeyebilir.

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

Hemen yanıt vermek için engelleme kodu da iptal edilmelidir

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

Bertaraf etmeniz gerekir, eğer kesilen kodda ayrılmış birçok bellek kaynağı varsa, bunu yapmalısınız.


1
Gerçekten de bu GetViewablePhrases çağrısı için de geçerli olacaktır - ideal olarak bu da bir async çağrısı olacak ve bir seçenek olarak bir iptal belirteci alacaktır.
Çeltik

1

CanncelationTek ile bekleme yöntemlerini nasıl kullanacağınızı tam olarak anlamak için .net sınıflarından birine göz atmanızı öneririm, SeamaphoreSlim.cs'i aldım

    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;
    }

Sınıfın tamamını buradan da görüntüleyebilirsiniz, https://referencesource.microsoft.com/#mscorlib/system/threading/SemaphoreSlim.cs,6095d9030263f169

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.