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

C# 试题 33:Task 与 async/await

0641 以下代码输出什么?

难度: 实战

static async Task M() => throw new InvalidOperationException("early");

Task t = M();
Console.WriteLine(t.IsFaulted);
  • A. 抛 InvalidOperationException,async 方法同步抛异常
  • B. True,async 方法体内异常被捕获进返回的 Task
  • C. False
  • D. 编译失败
查看答案与解析

正确答案B

解析: async 方法(哪怕异常发生在第一个 await 之前)不会同步抛出,而是把异常捕获进返回的 Task;调用方通过 await 或观察 Task 才能拿到异常。IsFaulted 为 True 而调用处无异常。

关键点: async 方法的异常“迟到”,永远在返回的 Task 里。

0642 以下代码输出什么?

难度: 进阶

async Task M()
{
    Console.Write("a");
    await Task.CompletedTask;
    Console.Write("b");
}

await M();
Console.Write("c");
  • A. acb
  • B. a 后挂起,永不继续
  • C. abc,已完成任务的 await 不产生让步,同步继续
  • D. ab,最后一行不执行
查看答案与解析

正确答案C

解析: await 只在等待尚未完成的操作时才让步。Task.CompletedTask 已完成,await 直接内联继续,bc 之前打印,输出 abc。想要强制让步应使用 Task.Yield()

关键点: “await 一定异步”是误解;已完成 await 是同步续行。

0643 以下代码输出什么?

难度: 进阶

static async Task M()
{
    Console.WriteLine("sync");
}

await M();
Console.WriteLine("done");
  • A. 编译失败,async 方法必须有 await
  • B. 只输出 done
  • C. 抛 InvalidOperationException
  • D. 编译警告 CS1998,输出 sync / done,方法体同步执行
查看答案与解析

正确答案D

解析: 没有 await 的 async 方法仍能编译,但编译器给出 CS1998 警告;方法体同步执行完毕,返回已完成的 Task。这类“假异步”往往是遗留了 async 修饰符,应及时清理。

关键点: async 无 await = 编译警告 + 完全同步执行。

0644 以下代码运行结果是什么?

难度: 实战

async void Fire() => throw new InvalidOperationException("boom");

try { Fire(); }
catch (Exception) { Console.WriteLine("caught"); }

await Task.Delay(100);
  • A. 输出 caught,async void 的异常可被调用方捕获
  • B. 异常未被捕获,进程终止(async void 的异常无法由调用方捕获)
  • C. 编译失败
  • D. 输出 caught 后正常结束
查看答案与解析

正确答案B

解析: async void 不返回 Task,异常无处安放,只能抛到当前 SynchronizationContext(或线程池)上成为未处理异常,调用方的 try/catch 永远捕不到,进程会因此终止。async void 只允许用于事件处理器。

关键点: async void = 异常不可观察 + 进程崩溃风险。

0645 以下代码输出什么?

难度: 进阶

Task<int> t = Task.FromException<int>(new InvalidOperationException("x"));

try { _ = t.Result; }
catch (Exception e) { Console.WriteLine(e.GetType().Name); }
  • A. InvalidOperationException
  • B. TaskCanceledException
  • C. AggregateException,Result/Wait 包装异常,await 才会解包
  • D. 编译失败
查看答案与解析

正确答案C

解析: Task.ResultTask.Wait() 在任务故障时抛 AggregateException(把内部异常包一层);只有 await(或 GetAwaiter().GetResult())才会把内部异常直接抛出。想少一层包装就避免同步阻塞。

关键点: Result 抛 AggregateException 是新手与资深都常记反的差异。

0646 以下代码输出什么?

难度: 进阶

Task<int> t = Task.FromException<int>(new InvalidOperationException("x"));

try { _ = t.GetAwaiter().GetResult(); }
catch (Exception e) { Console.WriteLine(e.GetType().Name); }
  • A. AggregateException
  • B. TaskCanceledException
  • C. 编译失败
  • D. InvalidOperationException,GetAwaiter().GetResult() 与 await 一样解包一层
查看答案与解析

正确答案D

解析: GetAwaiter().GetResult() 的异常语义与 await 一致:直接抛出内部异常,不包 AggregateException;同时它在已完成的 Task 上调用不会死锁,是“同步取结果”的推荐写法。

关键点: 同步取结果用 GetResult,别用 Result/Wait。

0647 以下代码输出什么?

难度: 实战

Task all = Task.WhenAll(Array.Empty<Task>());

Console.WriteLine(all.IsCompletedSuccessfully);
  • A. True,空集合的 WhenAll 立即成功完成
  • B. False,永远不会完成
  • C. 抛 ArgumentException
  • D. 编译失败
查看答案与解析

正确答案A

解析: Task.WhenAll 接收空集合时返回已成功完成的任务(没有可等待的东西,立即成功)。写“批量等待”的通用代码时不需要对空集合特判。

关键点: WhenAll 对空输入是“立即成功”,不是挂起也不是异常。

0648 以下代码输出什么?

难度: 进阶

Task t = Task.FromException(new InvalidOperationException("boom"));

try { await Task.WhenAll(t); }
catch (Exception e) { Console.WriteLine(e.GetType().Name); }
  • A. AggregateException
  • B. InvalidOperationException,await WhenAll 时解包为内部异常
  • C. TaskCanceledException
  • D. 编译失败
查看答案与解析

正确答案B

解析: WhenAll 聚合所有任务的异常放进 AggregateException,但 await 它时只解包一层、直接抛第一个内部异常;只有访问 all.Exception 才能看到完整聚合。

关键点: await 解包、Exception 属性聚合,两者要分清。

0649 以下代码能编译吗?

难度: 实战

object gate = new();

lock (gate)
{
    await Task.Delay(1);
}
  • A. 正常运行
  • B. 运行抛 SynchronizationLockException
  • C. 编译失败,不能在 lock 语句体中 await(应使用 SemaphoreSlim 等异步原语)
  • D. 死锁
查看答案与解析

正确答案C

解析: 语言层面禁止在 lock 语句体内使用 await(编译错误 CS1996),因为锁要由同一线程释放而 await 会离开线程。跨 await 的互斥必须用 SemaphoreSlim.WaitAsync 等异步同步原语。

关键点: lock 与 await 不能共存,Monitor 不是异步原语。

0650 以下代码能编译吗?

难度: 进阶

static async Task<int> M()
{
    yield return 1;
}
  • A. 编译失败,async 方法不能包含 yield return(除非返回 IAsyncEnumerable 等异步流)
  • B. 正常运行
  • C. 运行抛 InvalidOperationException
  • D. 返回一个元素的 Task
查看答案与解析

正确答案A

解析: 普通 async Task 方法中不能使用 yield;只有返回 IAsyncEnumerable<T>/IAsyncEnumerator<T> 的 async 迭代器才能同时使用 await 与 yield(C# 8+ 异步流)。两者状态机不兼容。

关键点: 异步流是“async + IAsyncEnumerable”,不是“async Task + yield”。

0651 WinForms/WPF 的 UI 线程上执行以下代码,会发生什么?

难度: 实战

private void Button_Click(object sender, EventArgs e)
{
    WorkAsync().Wait();
}

async Task WorkAsync()
{
    await Task.Delay(10);
}
  • A. 正常完成
  • B. UI 线程死锁:UI 线程阻塞等待任务,而任务的延续要回到被阻塞的 UI 线程
  • C. 抛 TaskCanceledException
  • D. 编译失败
查看答案与解析

正确答案B

解析: UI 线程有 SynchronizationContext,await 的延续要回 UI 线程执行;.Wait() 又把 UI 线程阻塞住,两者互相等待形成死锁。控制台/ASP.NET Core 没有该上下文,同样的代码不会死锁——但依然不推荐同步阻塞。

关键点: 同步阻塞 + 同步上下文 = 死锁;全程 await 或 ConfigureAwait(false)。

0652 关于 ConfigureAwait(false) 的正确说法?

难度: 进阶

  • A. 让异步方法在调用线程同步执行
  • B. 只影响 .NET Framework
  • C. 让延续不回到捕获的 SynchronizationContext;没有同步上下文(如控制台、ASP.NET Core)时基本无效果
  • D. 会取消任务
查看答案与解析

正确答案C

解析: ConfigureAwait(false) 告诉当前 await:后续延续不必回到捕获的 SynchronizationContext。在控制台或 ASP.NET Core(默认无同步上下文)中它几乎没有实际差异;在 UI/旧 ASP.NET 场景它能避免死锁并减少上下文切换。

关键点: 它的作用对象是“延续的上下文”,不是“任务本身”。

0653 以下代码输出什么?

难度: 实战

static async Task<int> Inner()
{
    await Task.Delay(1);
    return 42;
}

Task t = Task.Run(Inner);
Console.WriteLine(t is Task<int>);
  • A. False,Task.Run 返回 Task<Task<int>>
  • B. 编译失败
  • C. 抛 InvalidOperationException
  • D. True,Task.Run 会展开内层任务,返回 Task<int>
查看答案与解析

正确答案D

解析: Task.Run(Func<Task<TResult>>) 有专门的“展开(unwrap)”语义:直接返回内层任务的任务类型,不会出现 Task<Task<int>> 嵌套。这与 Task.Factory.StartNew 不同(后者需要手动 Unwrap)。

关键点: Task.Run 自动展开,StartNew 不展开。

0654 控制台应用(无同步上下文)中,以下代码输出什么?

难度: 实战

Console.WriteLine(Thread.CurrentThread.IsThreadPoolThread);
await Task.Delay(1);
Console.WriteLine(Thread.CurrentThread.IsThreadPoolThread);
  • A. False / True,await 后的延续在线程池上运行
  • B. True / True
  • C. False / False
  • D. True / False
查看答案与解析

正确答案A

解析: 主线程不是线程池线程;await 未完成的操作后,控制台应用没有同步上下文,延续被调度到线程池线程上执行,因此第二行为 True。这解释了为什么异步代码里不能依赖“回到发起线程”。

关键点: 没有同步上下文时,await 之后的线程不保证是原线程。

0655 关于 ValueTask<T>Task<T>,哪项正确?

难度: 进阶

  • A. 两者完全等价
  • B. ValueTask 内部永远是 Task
  • C. ValueTask 一定比 Task 慢
  • D. ValueTask 旨在减少分配;同一实例不应被多次 await(契约上只能消费一次),Task 可以重复 await
查看答案与解析

正确答案D

解析: ValueTask 是为了在“结果立即可用”时省掉 Task 分配(同步完成时直接装值);但它的消费契约是“单次”:同一个 ValueTask 实例只应 await 一次(不能并发 await、不能存起来复用),而 Task 可任意多次 await。异步方法返回 ValueTask 时应确保调用方只消费一次。

关键点: ValueTask 省分配的前提是“一次性消费”。

0656 以下代码输出什么?

难度: 实战

static async Task M()
{
    Thread.Sleep(100);
    await Task.Delay(1);
}

var sw = Stopwatch.StartNew();
await M();
Console.WriteLine(sw.ElapsedMilliseconds >= 100);
  • A. False,async 方法不会阻塞调用线程
  • B. True,async 方法在第一个未完成 await 前同步执行,Thread.Sleep 阻塞了调用线程
  • C. 抛 InvalidOperationException
  • D. 编译失败
查看答案与解析

正确答案B

解析: async 方法体在到达第一个未完成 await 之前是“同步执行”的,期间的 Thread.Sleep(100) 实打实阻塞调用线程;await M() 返回前调用线程已经被睡掉 100ms。把耗时逻辑放在第一个 await 之前并不会“异步化”。

关键点: async 不改变同步段;只有 await 之后才可能切线程。

0657 以下代码输出什么?

难度: 进阶

static async Task<int> Inner()
{
    await Task.Delay(1);
    return 42;
}

Task t = Task.Factory.StartNew(Inner);
Console.WriteLine(t is Task<Task<int>>);
  • A. True,StartNew 返回 Task<Task<int>>,不展开,需要 Unwrap
  • B. False
  • C. 编译失败
  • D. 抛 InvalidOperationException
查看答案与解析

正确答案A

解析: Task.Factory.StartNew(Func<Task<int>>) 不识别内层任务,返回 Task<Task<int>>await 它只会得到内层 Task 而不是 42;需要手动 Unwrap() 或用 Task.Run

关键点: StartNew + async lambda = 双重任务,必须 Unwrap。

0658 以下代码输出什么?

难度: 实战

Task t = null!;

try { await t; }
catch (Exception e) { Console.WriteLine(e.GetType().Name); }
  • A. TaskCanceledException
  • B. NullReferenceException,对 null 任务调用 GetAwaiter
  • C. InvalidOperationException
  • D. 编译失败
查看答案与解析

正确答案B

解析: await t 编译成 t.GetAwaiter();t 为 null 时在 GetAwaiter 上抛 NullReferenceException。await 本身不检查 null,它只负责展开 awaiter。写通用扩展方法时先判空再 await。

关键点: await null 是运行时 NRE,不是编译期错误。

0659 以下代码输出什么?

难度: 进阶

Task<int> t = Task.FromResult(42);

Console.WriteLine(await t);
Console.WriteLine(await t);
  • A. 42 / 抛异常
  • B. 抛异常
  • C. 编译失败
  • D. 42 / 42,Task 可以多次 await
查看答案与解析

正确答案D

解析: Task 是“结果可重复读取”的对象,await 多少次都返回相同结果,不会消费掉。与 ValueTask 的单次消费契约形成对比。

关键点: Task 可复用,ValueTask 不可复用。

0660 哪种场景使用 async void 是可接受的?

难度: 实战

  • A. 事件处理器(如 UI 按钮点击事件)
  • B. 库的对外公开 API
  • C. 返回值的工厂方法
  • D. 构造函数
查看答案与解析

正确答案A

解析: async void 的异常无法被调用方捕获,唯一的合理场景是事件处理器(事件签名不允许返回 Task)。库 API、工厂、构造函数一律用 async Task/Task<T>,让调用方能观察异常与完成状态。

关键点: async void 是事件处理器的专用逃生口,不是通用返回类型。

官方资料

当前分类

C# 选择题

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

输入关键词开始搜索