返回题库高级 .NET 刷题C# 选择题 · 第 34 / 50 篇

C# 试题 34:取消、异常与上下文

0661 以下代码输出什么?

难度: 实战

var cts = new CancellationTokenSource();
Task t = Task.Run(() =>
{
    Thread.Sleep(300);
    Console.WriteLine("finish");
});

cts.Cancel();
await t;
  • A. 任务被中断,不输出 finish
  • B. 抛 OperationCanceledException
  • C. 输出 finish,取消是协作式的,Cancel 不会中止正在运行的代码
  • D. 编译失败
查看答案与解析

正确答案C

解析: 取消是协作式的:Cancel() 只翻转 token 状态,不会中断或杀死正在运行的代码;任务代码没有检查 token 就继续执行完,输出 finish。想要真正停止,代码必须周期性地检查 IsCancellationRequested 或调用 ThrowIfCancellationRequested

关键点: Cancel 是“请求”,不是“终止”;响应与否取决于任务代码。

0662 以下代码输出什么?

难度: 进阶

var cts = new CancellationTokenSource();
cts.Cancel();

Task t = Task.Run(() => cts.Token.ThrowIfCancellationRequested(), cts.Token);
try { await t; } catch (OperationCanceledException) { }

Console.WriteLine(t.Status);
  • A. Canceled,抛出的 OCE 携带的 token 与任务的 token 一致
  • B. Faulted
  • C. RanToCompletion
  • D. WaitingToRun
查看答案与解析

正确答案A

解析: 委托抛出的 OperationCanceledException 若携带的 token 与创建任务时传入的 token 是同一个,运行时就把任务标记为 Canceled 而不是 Faulted;await 它抛 TaskCanceledException

关键点: 取消身份匹配是任务进入 Canceled 状态的前提。

0663 以下代码输出什么?

难度: 实战

var cts = new CancellationTokenSource();
cts.Cancel();

Task t = Task.Run(() => cts.Token.ThrowIfCancellationRequested());
Console.WriteLine(t.Status);
  • A. Canceled
  • B. WaitingToRun
  • C. Faulted,OCE 携带的 token 与任务关联的 token(此处为 None)不一致,任务按故障处理
  • D. RanToCompletion
查看答案与解析

正确答案C

解析: Task.Run 未传 token 时任务关联的 token 是 CancellationToken.None;委托抛出的 OCE 携带的是 cts.Token,两者不匹配,运行时无法确认这是“本次任务的取消”,于是把任务标记为 Faulted。想获得 Canceled 状态必须把同一个 token 传给 Task.Run。

关键点: 取消身份不一致 = 取消被降级成故障。

0664 以下代码输出什么?

难度: 进阶

var cts = new CancellationTokenSource();
cts.Cancel();

Console.WriteLine("a");
cts.Token.Register(() => Console.WriteLine("b"));
Console.WriteLine("c");
  • A. a / b / c,Register 遇到已取消的 token 会同步执行回调
  • B. a / c,已取消后注册不再回调
  • C. 编译失败
  • D. 抛 ObjectDisposedException
查看答案与解析

正确答案A

解析: Register 在 token 已处于取消状态时不会拒绝注册,而是在当前线程上同步调用回调:因此输出 abc。这也是“注册回调的时机”容易踩的坑——回调可能在 Register 返回前就已经执行。

关键点: 已取消 token 的 Register 同步触发回调,注册前要处理好状态。

0665 以下代码输出什么?

难度: 实战

var cts = new CancellationTokenSource();
var reg = cts.Token.Register(() => Console.WriteLine("cb"));

reg.Dispose();
cts.Cancel();
Console.WriteLine("done");
  • A. cb / done
  • B. cb 后抛异常
  • C. done,Dispose 注销回调,Cancel 不再调用它
  • D. 编译失败
查看答案与解析

正确答案C

解析: Register 返回的 CancellationTokenRegistration 实现 IDisposable:Dispose() 把回调从注册表中移除(若尚未触发),之后 Cancel 不会调用它。释放类时应把注册当作资源一并释放,避免回调泄漏到取消时刻。

关键点: 取消注册要 Dispose,否则回调可能在使用期外被触发。

0666 以下代码输出什么?

难度: 进阶

var cts = new CancellationTokenSource();
cts.Token.Register(() => throw new InvalidOperationException("boom"));

try { cts.Cancel(); }
catch (Exception e) { Console.WriteLine(e.GetType().Name); }
  • A. 异常被吞掉
  • B. InvalidOperationException,回调异常原样传播
  • C. 编译失败
  • D. AggregateException,Cancel 把回调抛出的异常包装成 AggregateException
查看答案与解析

正确答案D

解析: 取消回调在调用 Cancel 的线程上同步执行;回调抛异常时 Cancel 会把它包装成 AggregateException 抛给调用者(有多个回调异常时聚合全部)。因此注册回调里不应抛异常,否则 Cancel 的调用方会“无故”收到异常。

关键点: 回调异常以 AggregateException 形式从 Cancel 逃逸。

0667 以下代码输出什么?

难度: 实战

var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(50));

try { await Task.Delay(10_000, cts.Token); }
catch (OperationCanceledException) { Console.WriteLine("cancelled"); }
  • A. 等待 10 秒后输出 cancelled
  • B. 约 50ms 后输出 cancelled,CTS 定时自动取消
  • C. 抛 TimeoutException
  • D. 编译失败
查看答案与解析

正确答案B

解析: new CancellationTokenSource(TimeSpan) 内部起一个定时器,到期自动调用 CancelTask.Delay(10_000, cts.Token) 收到取消后抛 OperationCanceledException,约 50ms 后输出 cancelled

关键点: CTS 构造参数是“自动取消倒计时”,不是超时异常机制。

0668 以下代码输出什么?

难度: 进阶

var cts = new CancellationTokenSource();
cts.Cancel();

try { await Task.Delay(1000, cts.Token); }
catch (Exception e) { Console.WriteLine(e.GetType().Name); }
  • A. OperationCanceledException
  • B. AggregateException
  • C. TimeoutException
  • D. TaskCanceledException
查看答案与解析

正确答案D

解析: Task.Delay 被取消时抛的是 TaskCanceledException(它继承自 OperationCanceledException),catch (Exception) 捕获到的具体类型是 TaskCanceledException。按 OperationCanceledException 捕获能覆盖两种取消,但诊断时要区分具体类型。

关键点: 捕获 OCE 能兜住取消,但实际抛出的常常是 TaskCanceledException。

0669 任务的异常从未被观察(未 await、未访问 Exception),会发生什么?

难度: 进阶

  • A. 进程立即崩溃
  • B. 默认不崩溃;任务被 GC 回收时触发 UnobservedTaskException 事件(.NET 4.5+ 默认不终止进程)
  • C. 异常自动重抛到发起线程
  • D. 异常静默消失且无任何通知
查看答案与解析

正确答案B

解析: 未被观察的任务异常不会同步冒泡;任务被垃圾回收时运行时引发 TaskScheduler.UnobservedTaskException 事件,.NET 4.5 起默认不因此终止进程。但它仍是 bug:日志里观察不到、异常被静默吞掉,应显式观察或 await。

关键点: 未观察异常会延迟到 GC 才暴露,别依赖“没人看就没事”。

0670 await 的延续默认在哪里执行?

难度: 实战

  • A. 永远在发起线程
  • B. 永远在线程池
  • C. 捕获的 SynchronizationContext;无同步上下文则当前 TaskScheduler;都没有则线程池
  • D. 在新建的专用线程
查看答案与解析

正确答案C

解析: await 在等待点捕获“上下文”:先看 SynchronizationContext.Current(UI/旧 ASP.NET 有),没有则看当前 TaskScheduler,都没有(如控制台)就在线程池上调度延续。ConfigureAwait(false) 的作用就是跳过前两步。

关键点: 延续的线程由“捕获的上下文”决定,不是固定的。

0671 关于 OperationCanceledException 与 TaskCanceledException 的关系,哪项正确?

难度: 进阶

  • A. 两者毫无关系
  • B. OperationCanceledException 继承自 TaskCanceledException
  • C. 两者是同一类型
  • D. TaskCanceledException 继承自 OperationCanceledException,捕获 OCE 即可覆盖两类取消
查看答案与解析

正确答案D

解析: TaskCanceledException : OperationCanceledException。取消任务时(Task.Delay、WhenAll 遇取消等)抛 TaskCanceledException;catch (OperationCanceledException) 能同时捕获两者,但要注意它也会捕获用户代码自己抛的 OCE,可能掩盖“不是取消”的路径。

关键点: 捕获 OCE 覆盖两类取消,但要区分“取消源”再决定是否重抛。

0672 以下代码输出什么?

难度: 实战

var cts = new CancellationTokenSource();
Task t1 = Task.Delay(Timeout.Infinite, cts.Token);
Task t2 = Task.FromResult(1);

var all = Task.WhenAll(t1, t2);
cts.Cancel();
try { await all; } catch (OperationCanceledException) { }

Console.WriteLine(all.IsCanceled);
  • A. True,其中一个任务被取消时 WhenAll 结果也是 Canceled
  • B. False
  • C. 抛 AggregateException
  • D. 编译失败
查看答案与解析

正确答案A

解析: WhenAll 等待全部任务:t2 已成功,t1 因取消而进入 Canceled;整体在全部完成时没有故障、但有任务被取消,于是 WhenAll 结果也是 Canceled,await 抛 TaskCanceledException。

关键点: 部分任务取消会“传染”给 WhenAll 的最终状态。

0673 以下代码输出什么?

难度: 进阶

var cts = new CancellationTokenSource();
var token = cts.Token;

var t = Task.Run(async () =>
{
    await Task.Delay(10);
    token.ThrowIfCancellationRequested();
    return "ok";
}, token);
cts.Dispose(); // 只释放,未取消

Console.WriteLine(await t);
  • A. 抛 ObjectDisposedException
  • B. ok,Dispose 不等于 Cancel,已开始的操作用不到 token 时不受影响
  • C. 抛 OperationCanceledException
  • D. 编译失败
查看答案与解析

正确答案B

解析: CancellationTokenSource.Dispose() 只释放内部资源(定时器、注册表),并不会取消操作;token 仍然是“未取消”状态,任务正常完成输出 ok。取消必须显式调用 Cancel() 或依赖 CancelAfter

关键点: Dispose 与 Cancel 是两个独立动作,别混为一谈。

0674 以下代码输出什么?

难度: 进阶

var cts1 = new CancellationTokenSource();
var cts2 = new CancellationTokenSource();

using var linked = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token);
cts2.Cancel();

Console.WriteLine(linked.IsCancellationRequested);
  • A. False
  • B. 抛 ObjectDisposedException
  • C. True,任一源取消,链接的 CTS 也随之取消
  • D. 编译失败
查看答案与解析

正确答案C

解析: CreateLinkedTokenSource 把多个 token 合并成一个:只要任一源 token 被取消,链接源也会进入取消状态。注意链接源本身要 Dispose 以解除对源的监听,避免回调泄漏。

关键点: 多条件取消用链接源,“任一成立即取消”。

0675 以下代码输出什么?

难度: 实战

Task t = Task.Delay(10_000);

try { await t.WaitAsync(TimeSpan.FromMilliseconds(50)); }
catch (TimeoutException) { Console.WriteLine("timeout"); }
  • A. 输出 timeout,WaitAsync 超时抛 TimeoutException,原任务继续运行
  • B. 50ms 后输出 timeout,原任务被取消
  • C. 抛 TaskCanceledException
  • D. 编译失败
查看答案与解析

正确答案A

解析: Task.WaitAsync(timeout)(.NET 6+)给等待加超时:超时抛 TimeoutException,但被等待的原任务仍在后台运行(不会被取消)。想要超时同时取消原任务,应传入自己的 CancellationToken 并在超时后自行 Cancel。

关键点: WaitAsync 超时 ≠ 取消原任务,两者要分开管理。

0676 以下代码输出什么?

难度: 实战

var cts = new CancellationTokenSource();
var token = cts.Token;

try
{
    token.ThrowIfCancellationRequested();
    Console.WriteLine("no");
}
catch (OperationCanceledException)
{
    Console.WriteLine("cancelled");
}
  • A. 输出 cancelled
  • B. 抛 TaskCanceledException
  • C. 编译失败
  • D. 输出 no,未取消时 ThrowIfCancellationRequested 直接返回
查看答案与解析

正确答案D

解析: ThrowIfCancellationRequested 只在 IsCancellationRequested 为 true 时抛 OperationCanceledException;未取消时是空操作,继续执行输出 no。它是协作式取消的“检查点”。

关键点: 该方法平时零成本,取消时才抛异常。

0677 以下代码输出什么?

难度: 实战

var local = new AsyncLocal<int>();
local.Value = 1;

int v = await Task.Run(() => local.Value);
Console.WriteLine(v);
  • A. 0,AsyncLocal 不流向子任务
  • B. 编译失败
  • C. 抛 InvalidOperationException
  • D. 1,AsyncLocal 随执行上下文流入 Task.Run 的任务
查看答案与解析

正确答案D

解析: AsyncLocal<T> 的值随 ExecutionContext 流动:Task.Run 创建的任务继承调用方的 AsyncLocal 快照,因此任务内读到 1。注意流是“复制下行”,任务内修改不会回流到调用方。

关键点: AsyncLocal 下传不回流,跨异步边界传递“请求上下文”时要注意。

0678 以下代码输出什么?

难度: 进阶

Task t1 = Task.FromException(new InvalidOperationException("A"));
Task t2 = Task.FromException(new ArgumentException("B"));

var all = Task.WhenAll(t1, t2);
try { await all; } catch (Exception) { }

Console.WriteLine(all.Exception!.InnerExceptions.Count);
  • A. 1,只记录第一个异常
  • B. 0
  • C. 2,WhenAll 聚合所有任务的异常;await 只解包抛出第一个
  • D. 抛 NullReferenceException
查看答案与解析

正确答案C

解析: WhenAll 把每个任务的异常都收进结果的 AggregateException(这里 2 个);await 只把第一个内部异常解包抛出,其余异常只有通过 all.Exception 才能观察。只 catch await 会漏掉其他任务的异常。

关键点: await 只能拿到“第一个”,完整异常列表在 Exception 属性里。

0679 以下代码输出什么?

难度: 实战

static async Task M() => throw new OperationCanceledException();

Task t = M();
Console.WriteLine(t.Status);
  • A. Canceled,async 方法抛 OCE 时返回的任务进入 Canceled 状态
  • B. Faulted
  • C. RanToCompletion
  • D. 异常同步抛到调用方
查看答案与解析

正确答案A

解析: async 状态机识别到 OperationCanceledException 时,把返回的任务置为 Canceled(而非 Faulted),await 它抛 TaskCanceledException。这与“取消身份”规则一致:OCE 语义上等于“任务被取消”。

关键点: async 方法抛 OCE = Canceled 状态,区别于普通异常。

0680 以下代码输出什么?

难度: 进阶

var cts = new CancellationTokenSource();
cts.Cancel();

try { await Task.Delay(1000, cts.Token); }
catch (OperationCanceledException e)
{
    Console.WriteLine(e is TaskCanceledException);
}
  • A. False
  • B. 编译失败
  • C. 捕获不到异常
  • D. True,Task.Delay 取消时抛 TaskCanceledException,它是 OCE 的子类
查看答案与解析

正确答案D

解析:catch (OperationCanceledException) 能捕获 Task.Delay 的取消异常,且该异常的实际类型是 TaskCanceledException(OCE 的子类),is 判断为 True。这也说明:想区分“谁的取消”,应检查异常携带的 CancellationToken

关键点: 按 OCE 捕获没问题,但具体类型与取消来源要会用 token 分辨。

官方资料

当前分类

C# 选择题

查看全部分类 →
  1. 32C# 试题 32:释放模式与终结器20 题
  2. 33C# 试题 33:Task 与 async/await20 题
  3. 34C# 试题 34:取消、异常与上下文20 题
  4. 35C# 试题 35:ValueTask 与异步流20 题
  5. 36C# 试题 36:线程同步20 题
ESC

输入关键词开始搜索