A call to CancellationTokenSource.Cancel never returns
我遇到了一个对
这是怎么回事:
有一段代码,我们称之为工人代码吗?那是在等待一些异步代码。为了简单起见,让我们说这段代码正在等待 Task.Delay:
1 2 3 4 5 6 7 8 9 | try { await Task.Delay(5000, cancellationToken); // a€| } catch (OperationCanceledException) { // a€|. } |
就在一个€?the worker codea€?之前调用它在线程 T1 上执行的
延续(即 a€?awaita€? 之后的行或 catch 内的块)将稍后在 T1 或其他线程上执行,具体取决于一系列因素。
我希望线程 T2 通过返回到
接下来发生的事情令人惊讶。我看到在线程 T2 上,在调用
更多上下文
以下是有关实际代码作用的更多上下文:
有一个€?worker codea€?累积请求。一些客户代码正在提交请求。每隔几秒一个€?工人代码€?处理这些请求。已处理的请求将从队列中删除。
然而,偶尔,客户端代码?决定它达到了希望立即处理请求的程度。将此传达给一个€?工人代码€?它调用了一个方法
实际代码被精简为最简单的形式,代码可在 GitHub 上获得。
环境
该问题可以在控制台应用程序、适用于 Windows 的通用应用程序的后台代理和适用于 Windows Phone 8.1 的通用应用程序的后台代理中重现。
无法在适用于 Windows 的通用应用程序中重现该问题,其中代码按我预期的方式工作并且对
调用
我们来看看源码:
1 2 3 4 5 6 7 8 9 10 | public void Cancel() { Cancel(false); } public void Cancel(bool throwOnFirstException) { ThrowIfDisposed(); NotifyCancellation(throwOnFirstException); } |
这里是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | private void NotifyCancellation(bool throwOnFirstException) { // fast-path test to check if Notify has been called previously if (IsCancellationRequested) return; // If we're the first to signal cancellation, do the main extra work. if (Interlocked.CompareExchange(ref m_state, NOTIFYING, NOT_CANCELED) == NOT_CANCELED) { // Dispose of the timer, if any Timer timer = m_timer; if(timer != null) timer.Dispose(); //record the threadID being used for running the callbacks. ThreadIDExecutingCallbacks = Thread.CurrentThread.ManagedThreadId; //If the kernel event is null at this point, it will be set during lazy construction. if (m_kernelEvent != null) m_kernelEvent.Set(); // update the MRE value. // - late enlisters to the Canceled event will have their callbacks called immediately in the Register() methods. // - Callbacks are not called inside a lock. // - After transition, no more delegates will be added to the // - list of handlers, and hence it can be consumed and cleared at leisure by ExecuteCallbackHandlers. ExecuteCallbackHandlers(throwOnFirstException); Contract.Assert(IsCancellationCompleted,"Expected cancellation to have finished"); } } |
好的,现在要注意的是
1 2 3 4 5 6 7 8 9 10 11 | if (m_executingCallback.TargetSyncContext != null) { m_executingCallback.TargetSyncContext.Send(CancellationCallbackCoreWork_OnSyncContext, args); // CancellationCallbackCoreWork_OnSyncContext may have altered ThreadIDExecutingCallbacks, so reset it. ThreadIDExecutingCallbacks = Thread.CurrentThread.ManagedThreadId; } else { CancellationCallbackCoreWork(args); } |
我猜你现在开始明白我接下来要看的地方了……当然是
1 2 3 4 5 | // Register our cancellation token, if necessary. if (cancellationToken.CanBeCanceled) { promise.Registration = cancellationToken.InternalRegisterWithoutEC(state => ((DelayPromise)state).Complete(), promise); } |
嗯……那个
1 2 3 4 5 6 7 8 9 | internal CancellationTokenRegistration InternalRegisterWithoutEC(Action<object> callback, Object state) { return Register( callback, state, false, // useSyncContext=false false // useExecutionContext=false ); } |
啊。
这是
有点类似于
使用同一个线程比在
1 2 3 4 5 | public static void CancelWithBackgroundContinuations(this CancellationTokenSource) { Task.Run(() => CancellationTokenSource.Cancel()); cancellationTokenSource.Token.WaitHandle.WaitOne(); // make sure to only continue when the cancellation completed (without waiting for all the callbacks) } |
由于这里已经列出的原因,我相信您希望以零毫秒的延迟实际使用
CancelAfter 的源代码在这里。
它在内部使用 TimerQueueTimer 来发出取消请求。这没有记录,但应该可以解决 op\\ 的问题。
文档在这里。