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

C# 试题 37:并行、PLINQ 与 Channel

0721 关于并行化适用性,哪项正确?

难度: 进阶

  • A. 任何 for 循环改用 Parallel.For 都会更快
  • B. 并行化适合足够大、CPU 密集且可分割的工作;细粒度任务会被调度开销抵消,I/O 等待应优先使用异步而非占线程并行
  • C. Parallel.For 保证按索引顺序执行
  • D. Parallel.For 只适用于数组
查看答案与解析

正确答案B

解析: 并行化收益来自多个核心同时做 CPU 密集工作;任务太小则分区、调度、同步的开销超过收益,I/O 等待场景则浪费线程资源,应使用异步。Parallel 系列从不承诺执行顺序。

关键点: 并行适合大的 CPU 密集可分割工作,I/O 走异步。

0722 以下代码的结果是什么?

难度: 实战

int sum = 0;
Parallel.For(0, 10_000, i =>
{
    sum += i;
});

Console.WriteLine(sum);
  • A. 总是输出 49995000
  • B. 抛 AggregateException
  • C. 结果不确定:sum += i 是读-改-写,多个迭代并发执行存在数据竞争,通常小于 49995000
  • D. 编译失败
查看答案与解析

正确答案C

解析: sum += i 不是原子操作,并行迭代会交错读写,丢失更新。正确做法是各迭代用局部变量累加后再合并,或使用 Interlocked(此处 int 足够,注意期望和未溢出)。结果不能作为确定值依赖。

关键点: Parallel.For 体内共享可变状态是数据竞争,需局部聚合。

0723 以下代码哪项正确?

难度: 进阶

Parallel.For(0, 10, i =>
{
    Console.Write(i + " ");
});
  • A. 输出顺序不确定,但每个索引恰好执行一次;Parallel.For 不保证迭代顺序
  • B. 一定输出 0 1 2 3 4 5 6 7 8 9
  • C. 一定输出倒序
  • D. 部分索引可能被跳过
查看答案与解析

正确答案A

解析: Parallel.For 把索引区间分区交给多个线程并发执行,因此输出顺序不定;但每个索引都会且只会执行一次(除非异常提前终止)。依赖顺序的代码不应使用 Parallel 系列。

关键点: Parallel.For 无顺序保证,但有完整性保证。

0724 以下代码输出什么?

难度: 实战

try
{
    Parallel.For(0, 10, i =>
    {
        if (i == 3) throw new InvalidOperationException("boom");
    });
}
catch (Exception ex)
{
    Console.WriteLine(ex.GetType().Name);
}
  • A. InvalidOperationException
  • B. 什么都不输出
  • C. Exception
  • D. AggregateException:Parallel.For 把并行迭代中抛出的异常聚合后重新抛出
查看答案与解析

正确答案D

解析: 并行循环可能同时有多个迭代抛异常,因此 Parallel.For 把异常包装进 AggregateException 统一抛出。不要期望直接捕获原始异常类型。

关键点: Parallel 系列的异常以 AggregateException 形式出现。

0725 以下代码哪项正确?

难度: 实战

var items = Enumerable.Range(0, 10).ToList();

await Parallel.ForEachAsync(items, async (item, token) =>
{
    await Task.Delay(10, token);
    Console.Write(item + " ");
});
  • A. 输出严格按 0 到 9 顺序
  • B. 输出顺序不确定,但每个元素都会被处理一次;ForEachAsync 不保证处理顺序
  • C. 编译失败:body 不能是 async 方法
  • D. 一定抛 AggregateException
查看答案与解析

正确答案B

解析: Parallel.ForEachAsync(.NET 6+)的 body 是 Func<T, CancellationToken, ValueTask>,支持 async;元素按分区并发处理,顺序不保证,但每个元素处理一次。它返回 Task,可 await。

关键点: ForEachAsync 支持异步 body,顺序不保证。

0726 以下代码哪项正确?

难度: 进阶

await Parallel.ForEachAsync(
    Enumerable.Range(0, 100),
    new ParallelOptions { MaxDegreeOfParallelism = 4 },
    async (i, ct) => await Task.Delay(10, ct));
  • A. 同一时刻最多约 4 个 body 并发执行;MaxDegreeOfParallelism 是并发上限,运行时可能执行更少
  • B. 100 个 body 同时执行
  • C. 严格按顺序逐个执行
  • D. 编译失败:ForEachAsync 没有带 ParallelOptions 的重载
查看答案与解析

正确答案A

解析: MaxDegreeOfParallelism 是并发执行的 body 数量上限,运行时在资源不足或任务提前完成时可能低于该值,但不会超过。默认并发度与处理器数量相关,不适合所有场景。

关键点: MaxDegreeOfParallelism 是上限而非目标值。

0727 以下代码输出什么?

难度: 实战

try
{
    await Parallel.ForEachAsync(Enumerable.Range(0, 5), async (i, ct) =>
    {
        if (i == 2) throw new InvalidOperationException("x");
        await Task.Yield();
    });
}
catch (Exception ex)
{
    Console.WriteLine(ex.GetType().Name);
}
  • A. InvalidOperationException
  • B. TaskCanceledException
  • C. AggregateException:ForEachAsync 把 body 抛出的异常聚合到返回的 Task 中
  • D. 什么都不输出
查看答案与解析

正确答案C

解析: ForEachAsync 内部收集各并发 body 的异常,合并为 AggregateException 让返回的 Task 失败;await 时抛出的是 AggregateException,需要解包 InnerExceptions。

关键点: ForEachAsync 与 Parallel.For 一样聚合异常。

0728 处理大量 I/O 等待型任务(如逐个请求 HTTP 接口),哪项选型更合理?

难度: 进阶

  • A. 用 Parallel.For + Thread.Sleep 阻塞等待
  • B. Parallel.ForEachAsync 配合真正的异步等待,I/O 等待期间不占用线程;也可用 Channel 限流,两者都优于阻塞线程的并行
  • C. Parallel.ForEachAsync 只适合 CPU 密集任务
  • D. 必须用 Task.Run 包住每个请求
查看答案与解析

正确答案B

解析: I/O 密集任务等待的是外部响应,占住线程没有收益。ForEachAsync 的 body 内 await 真正的异步 I/O 时线程被释放,配合 MaxDegreeOfParallelism 即可限流;生产级限流也可用 Channel + 固定数量消费者。

关键点: I/O 密集用异步并发而非阻塞并行。

0729 以下代码哪项正确?

难度: 进阶

var result = Enumerable.Range(0, 10).AsParallel()
    .Select(x => x * 2)
    .ToList();
  • A. 一定按 0,2,4,…,18 排列
  • B. 一定乱序
  • C. 编译失败:AsParallel 不能与 Select 连用
  • D. 顺序不确定:AsParallel 默认不保证保持源顺序,需要 AsOrdered 才保证有序输出
查看答案与解析

正确答案D

解析: PLINQ 默认以分区并行执行并在需要时按效率合并输出,不承诺源顺序。若下游依赖顺序,必须调用 AsOrdered()(以合并开销为代价)或在最终阶段排序。

关键点: AsParallel 默认无顺序保证,AsOrdered 才有。

0730 以下代码输出什么?

难度: 实战

var result = Enumerable.Range(0, 10).AsParallel().AsOrdered()
    .Select(x => x * 2)
    .ToList();

Console.WriteLine(string.Join(",", result));
  • A. 0,2,4,6,8,10,12,14,16,18:AsOrdered 保持源顺序(以合并开销为代价)
  • B. 顺序随机
  • C. 编译失败:AsOrdered 与 AsParallel 不能连用
  • D. 抛 InvalidOperationException
查看答案与解析

正确答案A

解析: AsOrdered() 让 PLINQ 在保留并行执行的同时按源顺序输出结果,代价是结果合并阶段需要缓冲与排序。因此输出为源序列各元素乘 2 的有序列表。

关键点: AsOrdered 换顺序保证,付出合并开销。

0731 以下代码哪项正确?

难度: 实战

Enumerable.Range(0, 10).AsParallel().ForAll(x =>
{
    Console.Write(x + " ");
});
  • A. 一定输出 0 到 9 顺序
  • B. 输出顺序不确定:ForAll 跳过有序合并,直接并行执行副作用
  • C. 编译失败:ForAll 只接受 Action
  • D. 输出一定为空
查看答案与解析

正确答案B

解析: ForAll 不产生有序结果,直接在分区线程上执行委托,省略了合并排序,是“只做副作用”场景的优化选择。副作用本身要保证线程安全,顺序不可依赖。

关键点: ForAll 跳过有序合并,顺序无保证。

0732 以下代码输出什么?

难度: 实战

try
{
    Enumerable.Range(0, 10).AsParallel().Select(x =>
    {
        if (x == 4) throw new Exception("boom");
        return x;
    }).ToList();
}
catch (Exception ex)
{
    Console.WriteLine(ex.GetType().Name);
}
  • A. Exception
  • B. InvalidOperationException
  • C. AggregateException:PLINQ 把并行查询中的异常聚合抛出
  • D. 不输出:查询是惰性的,ToList 前不执行
查看答案与解析

正确答案C

解析: 查询在 ToList() 时被枚举执行,PLINQ 把各分区抛出的异常收集并包装为 AggregateException。注意异常在枚举(执行)时抛出,而不是在构造查询时。

关键点: PLINQ 异常聚合为 AggregateException,且发生在执行时。

0733 以下代码输出什么?

难度: 实战

var channel = Channel.CreateBounded<int>(3);

for (int i = 0; i < 3; i++)
    channel.Writer.TryWrite(i);

bool ok = channel.Writer.TryWrite(3);
Console.WriteLine(ok);
  • A. True
  • B. False:容量已满,TryWrite 不阻塞,满时立即返回 false(背压信号)
  • C. 抛 ChannelClosedException
  • D. 阻塞直到有空间
查看答案与解析

正确答案B

解析: TryWrite 是“尽力而为”的同步写入:容量满时立即返回 false,绝不阻塞。有界通道的背压主要靠满时 WriteAsync 挂起或 TryWrite 返回 false 体现。

关键点: TryWrite 满时返回 false,是背压的同步信号。

0734 以下代码输出什么?

难度: 进阶

var channel = Channel.CreateBounded<int>(1);

await channel.Writer.WriteAsync(1);        // 立即完成

var pending = channel.Writer.WriteAsync(2); // 容量满,等待消费者
Console.WriteLine(pending.IsCompleted);

Console.WriteLine(await channel.Reader.ReadAsync());
Console.WriteLine(await pending);
  • A. True
  • B. False\n2\n1
  • C. 抛 ChannelClosedException
  • D. False\n1\n2:有界通道满时 WriteAsync 挂起形成背压,消费者读走一个后写入完成
查看答案与解析

正确答案D

解析: 有界通道默认 FullMode.Wait:容量满时 WriteAsync 返回未完成的 ValueTask,消费者读取后腾出空间,该写入才完成。因此先输出 False,再读到 1,最后 pending 完成并返回 2。

关键点: 有界通道满时 WriteAsync 挂起,读取即背压释放。

0735 以下代码输出什么?

难度: 进阶

var channel = Channel.CreateBounded<int>(2, new BoundedChannelOptions(2)
{
    FullMode = BoundedChannelFullMode.DropOldest
});

channel.Writer.TryWrite(1);
channel.Writer.TryWrite(2);
channel.Writer.TryWrite(3);

Console.WriteLine(await channel.Reader.ReadAsync());
Console.WriteLine(await channel.Reader.ReadAsync());
  • A. 2\n3:DropOldest 在容量满时丢弃最旧项,1 被挤出
  • B. 1\n2
  • C. 3\n3
  • D. 抛 ChannelClosedException
查看答案与解析

正确答案A

解析: DropOldest 模式下,容量满时新写入会丢弃最旧的元素:写入 1、2 后容量满,写 3 时 1 被挤出,剩余 2、3。首次读得 2,再读得 3。

关键点: DropOldest 丢弃最旧项,其余写入模式还有 DropNewest/DropWrite。

0736 关于 Channel 的背压,哪项正确?

难度: 进阶

  • A. 无界通道自带背压
  • B. 有界通道与无界通道在背压上完全等价
  • C. 有界通道通过容量上限提供背压:满时 WriteAsync 挂起(默认 FullMode.Wait),无界通道写入永不阻塞,可能耗尽内存
  • D. 有界通道不能用于并发生产消费
查看答案与解析

正确答案C

解析: 无界通道的 WriteAsync/TryWrite 永不因容量阻塞,生产速度超过消费速度时队列无限增长直至 OOM;有界通道以容量为界,满时挂起写入者,把背压传导给生产者,这是流式管道的正确姿势。

关键点: 背压来自有界容量,无界通道可能内存失控。

0737 以下代码输出什么?

难度: 实战

var channel = Channel.CreateUnbounded<int>();
channel.Writer.TryComplete();

bool second = channel.Writer.TryComplete();
Console.WriteLine(second);
  • A. True
  • B. False:通道已完成后,再次 TryComplete 返回 false,不抛异常
  • C. 抛 InvalidOperationException
  • D. True,并重置通道
查看答案与解析

正确答案B

解析: TryComplete 在通道已处于完成状态时返回 false,不抛异常,适合“多个生产者各自尝试完成”的竞态场景。真正要抛异常提示重复完成,才用 Complete()

关键点: TryComplete 幂等安全,重复调用返回 false。

0738 以下代码的结果是什么?

难度: 实战

var channel = Channel.CreateUnbounded<int>();
channel.Writer.Complete();
channel.Writer.Complete();
  • A. 正常运行
  • B. 抛 ChannelClosedException
  • C. 第二次调用被忽略
  • D. 第二次 Complete() 抛 InvalidOperationException:通道已完成后 Complete 抛异常,应使用 TryComplete
查看答案与解析

正确答案D

解析: Complete() 在通道已完成后第二次调用会抛 InvalidOperationExceptionTryComplete() 则返回 false。多生产者场景应统一使用 TryComplete 避免竞态异常。

关键点: Complete 重复调用抛异常,TryComplete 才是安全版本。

0739 以下代码输出什么?

难度: 实战

var channel = Channel.CreateUnbounded<int>();
channel.Writer.TryComplete();

bool ok = channel.Writer.TryWrite(1);
Console.WriteLine(ok);
  • A. False:通道完成后 TryWrite 返回 false
  • B. True
  • C. 抛 ChannelClosedException
  • D. 抛 InvalidOperationException
查看答案与解析

正确答案A

解析: 通道完成后写入被拒绝:TryWrite 返回 false 而不抛异常;WriteAsync 则会抛 ChannelClosedException。生产者在收到 false 时通常应立即停止生产。

关键点: 完成后的写入:TryWrite 返回 false,WriteAsync 抛异常。

0740 以下代码输出什么?

难度: 实战

var channel = Channel.CreateUnbounded<int>();
await channel.Writer.WriteAsync(1);
channel.Writer.TryComplete();

var items = new List<int>();
await foreach (var item in channel.Reader.ReadAllAsync())
    items.Add(item);

Console.WriteLine(items.Count);
Console.WriteLine(await channel.Reader.WaitToReadAsync());
  • A. 1\nTrue
  • B. 1\nFalse:ReadAllAsync 读到完成结束;完成且清空后 WaitToReadAsync 返回 false
  • C. 1\n1
  • D. 抛 ChannelClosedException
查看答案与解析

正确答案B

解析: ReadAllAsync 在通道完成且缓冲清空后正常结束。之后再调用 WaitToReadAsync:通道已完成且无剩余数据,返回 false,表示读取方可以退出。这是“观察完成状态”的可靠方式。

关键点: WaitToReadAsync 返回 false 表示完成且无数据,读取方据此退出。

官方资料

当前分类

C# 选择题

查看全部分类 →
  1. 35C# 试题 35:ValueTask 与异步流20 题
  2. 36C# 试题 36:线程同步20 题
  3. 37C# 试题 37:并行、PLINQ 与 Channel20 题
  4. 38C# 试题 38:GC 与分配20 题
  5. 39C# 试题 39:反射与特性20 题
ESC

输入关键词开始搜索