1 回答
TA贡献1786条经验 获得超11个赞
我的问题是为什么
[7] Task delay has been cancelled.
在请求令牌取消的同一线程中执行?
这是因为用flagawait
安排它的任务延续ExecuteSynchronously
。我也认为这种行为令人惊讶,并且最初将其报告为一个错误(作为“按设计”关闭)。
更具体地说,await
捕获一个上下文,如果该上下文与正在完成任务的当前上下文兼容,则async
延续将直接在完成该任务的线程上执行。
要逐步完成它:
某些线程池线程“7”运行
cancellationTokenSource.Cancel()
。这会导致
CancellationTokenSource
进入取消状态并运行其回调。其中一个回调是
Task.Delay
. 该回调不是特定于线程的,因此它在线程 7 上执行。这会导致
Task
返回的 fromTask.Delay
被取消。已经从一个线程池线程安排了它的await
continuation,线程池线程都被认为是相互兼容的,所以async
continuation直接在线程7上执行。
提醒一下,线程池线程仅在有代码要运行时使用。当您使用await
to发送异步代码时Task.Run
,它可以在一个线程上运行第一部分(直到await
),然后在另一个线程上运行另一部分(在 之后await
)。
因此,由于线程池线程是可互换的,因此线程 7 在;之后继续执行该async
方法并不是“错误的”。await
这只是一个问题,因为现在 之后的代码在那个延续Cancel
上被阻塞了。async
请注意,如果我从主线程执行 cancellationTokenSource.Cancel() 那么输出看起来像预期的那样
这是因为 UI 上下文被认为与线程池上下文不兼容。因此,当Task
返回的 fromTask.Delay
被取消时,await
将看到它在 UI 上下文中而不是线程池上下文中,因此它将其继续排队到线程池而不是直接执行它。
有趣的是,当我替换
Task.Delay(TimeSpan.FromMinutes(1), cancellationTokenSource.Token)
为cancellationTokenSource.Token.ThrowIfCancellationRequested()
.NET 时,后台线程一直处于忙碌状态,并且输出再次符合预期
这不是因为线程“忙”。这是因为没有回调了。所以观察方法是轮询而不是被通知。
该代码设置一个计时器(通过Task.Delay
),然后将线程返回到线程池。当定时器计时结束后,从线程池中抓取一个线程,检查取消令牌源是否被取消;如果不是,则设置另一个计时器并将线程再次返回到线程池。本段的要点是,Task.Run
它不仅仅代表“一个线程”;它在执行代码时只有一个线程(即不在 an 中await
),并且线程可以在任何await
.
除非您混合使用阻塞代码和异步代码,否则await
使用的一般问题通常不是问题。ExecuteSynchronously
在那种情况下,最好的解决方案是将阻塞代码更改为异步代码。如果你不能这样做,那么你需要小心如何继续你async
在 之后阻塞的方法await
。这主要是TaskCompletionSource<T>
和的问题CancellationTokenSource
。TaskCompletionSource<T>
有一个很好的RunContinuationsAsynchronously
选项可以覆盖ExecuteSynchronously
标志;不幸的是,CancellationTokenSource
没有;你必须使用排队你Cancel
对线程池的调用Task.Run
。
奖励:为您的队友准备的测验。
- 1 回答
- 0 关注
- 93 浏览
添加回答
举报