Apakah cara yang benar untuk membatalkan token pembatalan digunakan dalam suatu tugas?

10

Saya memiliki kode yang membuat token pembatalan

public partial class CardsTabViewModel : BaseViewModel
{
   public CancellationTokenSource cts;

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

Kode yang menggunakannya:

await GetCards(cts.Token);


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

dan kode yang kemudian membatalkan Token Pembatalan ini jika pengguna menjauh dari layar tempat kode di atas berjalan:

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

Mengenai pembatalan, apakah ini cara yang benar untuk membatalkan token ketika sedang digunakan dalam Tugas?

Secara khusus saya memeriksa pertanyaan ini:

Penggunaan properti IsCancellationRequested?

dan itu membuat saya berpikir bahwa saya tidak melakukan pembatalan dengan cara yang benar atau mungkin dengan cara yang dapat menyebabkan pengecualian.

Juga, dalam hal ini setelah saya membatalkan maka haruskah saya melakukan cts.Dispose ()?

Alan2
sumber
Biasanya, gunakan metode Batal untuk mengomunikasikan permintaan pembatalan dan kemudian gunakan metode Buang untuk melepaskan memori. Anda dapat memeriksa sampel di tautan. docs.microsoft.com/en-us/dotnet/api/…
Wendy Zang - MSFT

Jawaban:

2

CancellationTokenSource.Cancel() adalah cara yang valid untuk memulai pembatalan.

Polling ct.IsCancellationRequestedmenghindari lemparan OperationCanceledException. Karena pemungutan suara, itu membutuhkan iterasi dari loop untuk menyelesaikan sebelum akan menanggapi permintaan pembatalan.

Jika GetViewablePhrases()dan CheckAvailability()dapat dimodifikasi untuk menerima a CancellationToken, ini dapat membuat pembatalan lebih cepat untuk merespons, dengan mengorbankanOperationCanceledException membuang.

"Haruskah aku melakukan cts. Mengapa ()?" bukankah itu mudah ...

"Selalu buang IDisposables secepatnya"

Lebih merupakan pedoman daripada aturan. Taskitu sendiri sekali pakai, namun hampir tidak pernah langsung dibuang dalam kode.

Ada kasus-kasus (ketika WaitHandleatau penangan panggilan balik digunakan) di mana membuang ctsakan membebaskan sumber daya / menghapus root GC yang sebaliknya hanya akan dibebaskan oleh Finalizer. Ini tidak berlaku untuk kode Anda sebagaimana adanya tetapi mungkin di masa depan.

Menambahkan panggilan ke Disposesetelah membatalkan akan menjamin bahwa sumber daya ini segera dibebaskan dalam versi kode yang akan datang.

Namun, Anda harus menunggu kode yang digunakan ctsuntuk menyelesaikan sebelum menelepon membuang, atau memodifikasi kode untuk menangani ObjectDisposedExceptiondari penggunaan cts(atau tokennya) setelah dibuang.

Peter Wishart
sumber
"menghubungkan OnDisappearing untuk membuang cts" Sepertinya ide yang sangat buruk, karena, masih digunakan di dalam tugas lain. Terutama jika seseorang kemudian mengubah desain (memodifikasi subtugas untuk menerima CancellationTokenparameter), Anda dapat membuang WaitHandlesementara utas lainnya sedang aktif menunggu di atasnya :(
Ben Voigt
1
Secara khusus, karena Anda membuat klaim bahwa "membatalkan melakukan pembersihan yang sama seperti membuang", tidak ada gunanya menelepon Disposedari OnDisappearing.
Ben Voigt
Aduh, aku merindukan bahwa kode dalam jawaban sudah memanggil Cancel...
Peter Wishart
Telah menghapus klaim tentang pembatalan melakukan pembersihan yang sama (yang saya baca di tempat lain), sejauh yang saya tahu satu-satunya pembersihan yang Canceldilakukan adalah timer internal (jika digunakan).
Peter Wishart
3

Secara umum saya melihat penggunaan yang wajar dari Batalkan Token dalam kode Anda, tetapi menurut Pola Tugas Async kode Anda mungkin tidak segera dibatalkan.

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

Untuk segera merespons, kode pemblokiran juga harus dibatalkan

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

Terserah Anda jika Anda harus Buang, jika ada banyak sumber daya memori yang dicadangkan dalam kode terputus, Anda harus melakukannya.

Fidel Orozco
sumber
1
Dan memang ini juga akan berlaku untuk panggilan ke GetViewablePhrases - idealnya ini juga akan menjadi panggilan async dan menerima token pembatalan sebagai opsi.
Paddy
1

Saya akan merekomendasikan Anda untuk melihat pada salah satu kelas .net untuk sepenuhnya memahami cara menangani metode menunggu dengan CanncelationToken, saya mengambil 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;
    }

Anda juga dapat melihat seluruh kelas di sini, https://referencesource.microsoft.com/#mscorlib/system/threading/SemaphoreSlim.cs,6095d9030263f169

Muhab
sumber