[英]Is the correct way to cancel a cancellation token used in a task?
我有創建取消令牌的代碼
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 Token 的合理使用,但根據 Task Async Pattern,您的代碼可能不會立即取消。
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
是否必須Dispose由你決定,如果被中斷的代碼中保留了很多內存資源,你應該這樣做。
CancellationTokenSource.Cancel()
是開始取消的有效方式。
輪詢ct.IsCancellationRequested
避免拋出OperationCanceledException
。 因為它是輪詢,所以它需要循環迭代才能完成,然后才能響應取消請求。
如果可以修改GetViewablePhrases()
和CheckAvailability()
以接受CancellationToken
,這可能會使取消響應更快,代價是拋出OperationCanceledException
。
“我應該做一個 cts.Dispose() 嗎?” 是不是那么簡單...
“始終盡快處理 IDisposables”
與其說是規則,不如說是指南。 Task
本身是一次性的,但幾乎從未直接在代碼中處理過。
在某些情況下(當使用WaitHandle
或取消回調處理程序時)處置cts
將釋放資源/刪除 GC 根,否則只能由終結器釋放。 這些不適用於您的代碼,但將來可能適用。
在取消后添加對Dispose
的調用將保證在未來的代碼版本中立即釋放這些資源。
但是,您必須在調用 dispose 之前等待使用cts
的代碼完成,或者修改代碼以在處理后使用cts
(或其令牌)處理ObjectDisposedException
。
我建議您查看 .net 類之一,以完全了解如何使用 CanncelationToken 處理等待方法,我選擇了 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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.