Là cách chính xác để hủy mã thông báo hủy được sử dụng trong tác vụ?


10

Tôi có mã tạo mã thông báo hủy

public partial class CardsTabViewModel : BaseViewModel
{
   public CancellationTokenSource cts;

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

Mã sử ​​dụng nó:

await GetCards(cts.Token);


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

và mã sau đó sẽ hủy Mã thông báo hủy này nếu người dùng di chuyển ra khỏi màn hình nơi mã ở trên đang chạy:

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

Về hủy bỏ, đây có phải là cách chính xác để hủy mã thông báo khi nó được sử dụng trong Tác vụ không?

Đặc biệt tôi đã kiểm tra câu hỏi này:

Sử dụng tài sản IsCancnameRequested?

và nó khiến tôi nghĩ rằng tôi không thực hiện việc hủy bỏ đúng cách hoặc có thể theo cách có thể gây ra ngoại lệ.

Ngoài ra, trong trường hợp này sau khi tôi đã hủy thì tôi có nên thực hiện một cts.Dispose () không?


Thông thường, sử dụng phương thức Hủy để truyền đạt yêu cầu hủy và sau đó sử dụng phương thức Vứt bỏ để giải phóng bộ nhớ. Bạn có thể kiểm tra các mẫu trong liên kết. docs.microsoft.com/en-us/dotnet/api/ từ
Wendy Zang - MSFT

Câu trả lời:


2

CancellationTokenSource.Cancel() là một cách hợp lệ để bắt đầu hủy bỏ.

Bỏ phiếu ct.IsCancellationRequestedtránh ném OperationCanceledException. Bởi vì bỏ phiếu của nó, nó yêu cầu lặp lại vòng lặp để hoàn thành trước khi nó sẽ đáp ứng yêu cầu hủy bỏ.

Nếu GetViewablePhrases()CheckAvailability()có thể được sửa đổi để chấp nhận a CancellationToken, điều này có thể khiến việc hủy phản hồi nhanh hơn, với chi phí đã OperationCanceledExceptionbị ném.

"tôi có nên làm một cts.Dispose () không?" không phải là đơn giản ...

"Luôn loại bỏ IDisposeables càng sớm càng tốt"

Là nhiều hơn một hướng dẫn hơn là một quy tắc. Taskbản thân nó là dùng một lần, nhưng hầu như không bao giờ được xử lý trực tiếp trong mã.

Có những trường hợp (khi WaitHandlehoặc hủy bỏ trình xử lý gọi lại được sử dụng) trong đó việc xử lý ctssẽ giải phóng tài nguyên / xóa gốc GC mà nếu không thì Finalizer sẽ được giải phóng. Chúng không áp dụng cho mã của bạn vì nó đứng nhưng có thể trong tương lai.

Thêm một cuộc gọi đến Disposesau khi hủy sẽ đảm bảo rằng các tài nguyên này được giải phóng kịp thời trong các phiên bản mã trong tương lai.

Tuy nhiên, bạn phải đợi mã sử dụng ctskết thúc trước khi gọi xử lý hoặc sửa đổi mã để xử lý ObjectDisposedExceptionkhi sử dụng cts(hoặc mã thông báo) sau khi xử lý.


"kết nối OnDisappear để loại bỏ cts" Có vẻ như là một ý tưởng rất tồi, bởi vì, nó vẫn được sử dụng trong một nhiệm vụ khác. Đặc biệt nếu sau này ai đó thay đổi thiết kế (sửa đổi các nhiệm vụ để chấp nhận CancellationTokentham số), bạn có thể xử lý WaitHandletrong khi một luồng khác đang tích cực chờ đợi nó :(
Ben Voigt

1
Cụ thể, vì bạn đã đưa ra tuyên bố rằng "hủy thực hiện việc dọn dẹp giống như xử lý", nên việc gọi Disposetừ đó là vô nghĩa OnDisappearing.
Ben Voigt

Rất tiếc, tôi đã bỏ lỡ rằng mã trong câu trả lời đã gọi Cancel...
Peter Wishart

Đã xóa yêu cầu về việc hủy thực hiện cùng một dọn dẹp (mà tôi đã đọc ở nơi khác trên đó), theo như tôi có thể nói rằng việc dọn dẹp duy nhất Cancelthực hiện là bộ đếm thời gian nội bộ (nếu được sử dụng).
Peter Wishart

3

Nói chung, tôi thấy việc sử dụng hợp lý Hủy mã thông báo trong mã của bạn, nhưng theo Mẫu không đồng bộ tác vụ, mã của bạn không thể bị hủy ngay lập tức.

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

Để trả lời ngay, mã chặn cũng nên được hủy

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

Tùy thuộc vào bạn nếu bạn phải Loại bỏ, nếu có nhiều tài nguyên bộ nhớ được dự trữ trong mã bị gián đoạn, bạn nên làm điều đó.


1
Và thực tế, điều này cũng sẽ áp dụng cho lệnh gọi GetViewablePh cụm từ - lý tưởng đây cũng là một cuộc gọi không đồng bộ và nhận mã thông báo hủy làm tùy chọn.
Lúa

1

Tôi khuyên bạn nên xem một trong các lớp .net để hiểu đầy đủ cách xử lý các phương thức chờ với CanncelationToken, tôi đã chọn 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;
    }

Bạn cũng có thể xem cả lớp ở đây, https://referencesource.microsoft.com/#mscorlib/system/threading/SemaphoreSlim.cs,6095d9030263f169

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.