- android - RelativeLayout 背景可绘制重叠内容
- android - 如何链接 cpufeatures lib 以获取 native android 库?
- java - OnItemClickListener 不起作用,但 OnLongItemClickListener 在自定义 ListView 中起作用
- java - Android 文件转字符串
我有一种情况,对 CancellationTokenSource.Cancel
的调用永远不会返回。相反,在 Cancel
被调用之后(在它返回之前),执行将继续执行被取消代码的取消代码。如果被取消的代码随后没有调用任何可等待的代码,那么最初调用 Cancel
的调用者永远不会取回控制权。这很奇怪。我希望 Cancel
简单地记录取消请求并立即返回独立于取消本身。事实上,调用 Cancel
的线程最终会执行属于被取消操作的代码,并且在返回给 Cancel
的调用者之前这样做看起来像框架中的错误。
这是怎么回事:
有一段代码,我们称之为“工作代码”,它正在等待一些异步代码。为简单起见,假设此代码正在等待 Task.Delay:
try
{
await Task.Delay(5000, cancellationToken);
// …
}
catch (OperationCanceledException)
{
// ….
}
就在“工作代码”调用 Task.Delay
之前,它正在线程 T1 上执行。continuation(即“await”之后的行或 catch 内的 block )稍后将在 T1 或其他线程上执行,具体取决于一系列因素。
Task.Delay
。此代码调用 cancellationToken.Cancel
。对 Cancel
的调用是在线程 T2 上进行的。我希望线程 T2 通过返回到 Cancel
的调用者来继续。我还希望看到 catch (OperationCanceledException)
的内容很快在线程 T1 或 T2 以外的某个线程上执行。
接下来发生的事情令人惊讶。我看到在线程 T2 上,调用 Cancel
后,执行会立即继续 catch (OperationCanceledException)
中的 block 。当 Cancel
仍在调用堆栈中时,就会发生这种情况。就好像对 Cancel
的调用被它被取消的代码劫持了一样。下面是显示此调用堆栈的 Visual Studio 屏幕截图:
更多上下文
这里有一些关于实际代码作用的更多上下文:有一个累积请求的“ worker 代码”。一些“客户端代码”正在提交请求。每隔几秒钟,“工作代码”就会处理这些请求。已处理的请求将从队列中删除。然而,偶尔,“客户端代码”决定它到达了它希望立即处理请求的地步。为了将此信息传达给“工作代码”,它调用了“工作代码”提供的方法 Jolt
。由“客户端代码”调用的方法 Jolt
通过取消由 worker 代码主循环执行的 Task.Delay
来实现此功能。工作人员的代码已取消其 Task.Delay
并继续处理已排队的请求。
实际代码被简化为最简单的形式,代码为 available on GitHub .
环境
此问题可以在控制台应用程序、Windows 通用应用程序后台代理和 Windows Phone 8.1 通用应用程序后台代理程序中重现。
此问题无法在适用于 Windows 的通用应用程序中重现,其中代码按我预期的方式工作,并且对 Cancel
的调用会立即返回。
最佳答案
CancellationTokenSource.Cancel
不只是设置 IsCancellationRequested
标志。
CancallationToken
类有一个 Register
method ,它允许您注册将在取消时调用的回调。这些回调由 CancellationTokenSource.Cancel
调用。
让我们来看看source code :
public void Cancel()
{
Cancel(false);
}
public void Cancel(bool throwOnFirstException)
{
ThrowIfDisposed();
NotifyCancellation(throwOnFirstException);
}
这是 NotifyCancellation
方法:
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");
}
}
好的,现在要注意的是 ExecuteCallbackHandlers
可以在目标上下文或当前上下文中执行回调。我让你看看ExecuteCallbackHandlers
method source code因为在这里包含它有点太长了。但有趣的是:
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);
}
我猜你现在开始明白我接下来要看的地方了……当然是Task.Delay
。让我们看看它的source code :
// Register our cancellation token, if necessary.
if (cancellationToken.CanBeCanceled)
{
promise.Registration = cancellationToken.InternalRegisterWithoutEC(state => ((DelayPromise)state).Complete(), promise);
}
嗯……那是什么InternalRegisterWithoutEC
method ?
internal CancellationTokenRegistration InternalRegisterWithoutEC(Action<object> callback, Object state)
{
return Register(
callback,
state,
false, // useSyncContext=false
false // useExecutionContext=false
);
}
唉。 useSyncContext=false
- 这解释了您看到的行为,因为 ExecuteCallbackHandlers
中使用的 TargetSyncContext
属性将为 false。由于未使用同步上下文,取消在 CancellationTokenSource.Cancel
的调用上下文中执行。
关于c# - 对 CancellationTokenSource.Cancel 的调用永远不会返回,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31495411/
这是我正在使用的代码。我想要的是当屏幕出现时,while循环中的代码运行。当屏幕消失时,我需要它停止。有人可以就我是否正确使用 CancellationTokenSource 来提供此功能的建议。我还
假设我创建了一个任务 bool aBoolean = true; Task.Factory.StartNew(() => while(aBooloean) { ...
有人可以举个例子,我应该使用带有三个参数的下一个方法。 var tokenSource = new CancellationTokenSource(); var token = tokenSource
我有一个这样的 Worker 类: public class Worker { private List _someObjectList = null; public Worker(S
在这种情况下,如果用户按回车键取消任务,则由 ContinueWith Hook 的其他任务将运行,但根据 AggregateException,情况并非如此 尽管 ContinueWith 中的显式
这个问题在这里已经有了答案: When to dispose CancellationTokenSource? (7 个答案) 关闭 9 年前。 我应该什么时候处理 CancellationToke
CancellationTokenSource 类是一次性的。快速查看 Reflector 证明使用了 KernelEvent,这是一种(很可能)非托管资源。由于 CancellationTokenS
我有一个这样的应用程序: 我有一个应用程序,其中一部分代码在带有计时器延迟的循环中运行。在申请开始时,我声明: public static CancellationTokenSource tokenS
正如我阅读文档 CancellationTokenSource.Cancel 不应该抛出异常。 CancellationTokenSource.Cancel 在 cts.Cancel() 调用下方;导
我开始在 MacBook Pro 上使用 Xamarin Studio 进行 Xamarin.Forms 开发。我构建了一个应用程序,其目的是查询 PrestaShop 网站、检索产品并显示它们。 在
正如我阅读文档 CancellationTokenSource.Cancel 不应该抛出异常。 CancellationTokenSource.Cancel 在 cts.Cancel() 调用下方;导
我有一个接受 CancellationToken 的方法,该方法允许该方法的用户取消其正在执行的操作。在该方法中,我使用此 CancellationToken 以及 CancellationToken
static void Main(string[] args) { CancellationTokenSource cts = new CancellationTokenSou
我正在尝试根据这篇文章实现一些重试逻辑(但有任务) Cleanest way to write retry logic? 重试逻辑的想法是在给定的时间后执行第二个任务触发取消 void Main()
我想知道使用 CancellationTokenSource 结束循环任务和退出标志之间是否有任何区别 CancellationTokenSource: CancellationTokenSource
我想知道使用 CancellationTokenSource 结束循环任务和退出标志之间是否有任何区别 CancellationTokenSource: CancellationTokenSource
我看到这段代码在我面前,我很怀疑: CancellationTokenSource _cts; public void Dispose(); { _cts.Cancel(); _cts
发布Async Targeting Pack提示我使用 ILSpy看看是什么Task-based Asynchronous Pattern (TAP)那里提供了扩展方法(其中一些我已经自己实现用于 V
这与我的另一个问题有关How to cancel background printing . 我试图更好地理解 CancellationTokenSource 模型以及如何跨线程边界使用它。 我有一个
例如,如果我想取消 Dispose() 中的某个操作调用(其中 can be called multiple times ),然后我需要写 public void Dispose() { if
我是一名优秀的程序员,十分优秀!