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

C# 试题 42:HttpClient 与网络基础

0821 高并发、长时间运行下,这段代码最可能暴露什么问题?

难度: 实战

for (int i = 0; i < 10_000; i++)
{
    using var client = new HttpClient();
    _ = await client.GetStringAsync("https://example.com");
}
  • A. 每次都能复用连接,没有问题
  • B. 抛 ObjectDisposedException,client 被过早释放
  • C. 每个请求都新建 HttpClient 并释放底层 Handler,连接无法复用,最终端口/套接字耗尽
  • D. 编译失败
查看答案与解析

正确答案C

解析: using 释放 HttpClient 时也会释放底层 Handler 并关闭连接,新请求又要重新建连;高吞吐下大量连接进入 TIME_WAIT,最终抛 SocketException(端口/套接字耗尽)。HttpClient 被设计为长生命周期单例。

关键点: HttpClient 生命周期是“一次创建、全局复用”。

0822 未释放响应对象,会产生什么后果?

难度: 实战

using var client = new HttpClient();
for (int i = 0; i < 10_000; i++)
{
    var resp = await client.GetAsync("https://example.com");
    _ = await resp.Content.ReadAsStringAsync();
}
  • A. 连接不能归还连接池,长期运行会连接耗尽
  • B. 无影响,响应内容读取后连接自动归还
  • C. 抛 OutOfMemoryException
  • D. 编译失败
查看答案与解析

正确答案A

解析: 响应读取完不代表连接已归还,连接要等 HttpResponseMessage(及其 Content)被释放(或流被读完并释放)后才回到连接池复用;一直不释放会慢慢耗尽连接。

关键点:using 释放响应,连接才能回池。

0823 以下代码输出什么?

难度: 进阶

using var client = new HttpClient();
var resp = await client.GetAsync("https://example.com");

var first = await resp.Content.ReadAsStringAsync();
var second = await resp.Content.ReadAsStringAsync();

Console.WriteLine(second.Length);
  • A. 输出与 first.Length 相同
  • B. 抛 InvalidOperationException
  • C. 抛 ObjectDisposedException
  • D. 输出 0,HttpContent 只能读取一次,第二次读取流已到末尾
查看答案与解析

正确答案D

解析: HttpContent 的读取是前向的,第一次 ReadAsStringAsync 把内容流读到末尾;再次调用不会重绕,读到的是空内容,长度为 0。需要复用正文应先把字节缓存到自己的缓冲区。

关键点: 响应内容只能消费一次,别指望重复读取。

0824 以下代码输出什么?

难度: 实战

using var client = new HttpClient { Timeout = TimeSpan.FromMilliseconds(1) };
try
{
    _ = await client.GetStringAsync("https://example.com");
}
catch (Exception ex)
{
    Console.WriteLine(ex.GetType().Name);
}
  • A. 输出 TimeoutException
  • B. 输出 TaskCanceledException,HttpClient 超时以取消形式呈现
  • C. 输出 HttpRequestException
  • D. 无输出,超时被静默吞掉
查看答案与解析

正确答案B

解析: HttpClient 的 Timeout 到点后通过内部取消取消任务,调用方收到的是 TaskCanceledException(继承自 OperationCanceledException),而不是 TimeoutException。按异常类型 catch 时最容易踩这个坑。

关键点: 超时 = 取消,异常类型是 TaskCanceledException

0825 以下代码输出什么?

难度: 进阶

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };

try
{
    _ = await client.GetStringAsync("https://example.com", cts.Token);
}
catch (OperationCanceledException)
{
    Console.WriteLine(cts.IsCancellationRequested);
}

(服务器 5 秒后仍无响应,Timeout 先触发)

  • A. 输出 True
  • B. 不进入 catch,超时被吞掉
  • C. 输出 False,超时由 HttpClient 内部计时取消触发,与调用方 token 无关
  • D. 输出 TimeoutException
查看答案与解析

正确答案C

解析: HttpClient.Timeout 用的是它自己的内部计时取消,不会置位调用方传入的 CancellationToken,因此 catch 里 cts.IsCancellationRequested 为 False。想区分“超时”与“调用方取消”,就检查调用方 token 是否真的被取消。

关键点: 超时与取消都表现为 OperationCanceledException,靠 token 状态区分。

0826 以下代码输出什么?

难度: 实战

using var client = new HttpClient();
using var resp = await client.GetAsync("https://example.com/not-found");
Console.WriteLine(resp.IsSuccessStatusCode);
  • A. 输出 False,404 不会自动抛异常
  • B. 抛 HttpRequestException
  • C. 输出 True
  • D. 抛 WebException
查看答案与解析

正确答案A

解析: 只要请求成功送达并收到响应,无论状态码是多少,GetAsync 都不会抛异常;404 表现为 IsSuccessStatusCode == false。需要主动检查状态码或调用 EnsureSuccessStatusCode

关键点: 非 2xx 状态码默认不抛异常。

0827 以下代码输出什么?

难度: 实战

using var client = new HttpClient();
try
{
    using var resp = await client.GetAsync("https://example.com/not-found");
    resp.EnsureSuccessStatusCode();
}
catch (HttpRequestException ex)
{
    Console.WriteLine(ex.GetType().Name + " " + (int)ex.StatusCode!.Value);
}
  • A. 输出 HttpRequestException 0
  • B. 输出 HttpRequestException 404,EnsureSuccessStatusCode 对非成功状态抛 HttpRequestException 并携带 StatusCode
  • C. 输出 WebException 404
  • D. 无输出
查看答案与解析

正确答案B

解析: EnsureSuccessStatusCode 对非 2xx 抛 HttpRequestException,且自 .NET 5 起异常带有 StatusCode 属性,可取到 404,方便调用方区分错误类型。

关键点: HttpRequestException.StatusCode 可用于精确判断失败状态。

0828 以下代码中请求会发往哪个地址?

难度: 进阶

using var client = new HttpClient { BaseAddress = new Uri("https://example.com/api") };
var resp = await client.GetAsync("users");
Console.WriteLine(resp.RequestMessage?.RequestUri);
  • A. 输出 https://example.com/api/users
  • B. 输出 https://example.com/api
  • C. 抛 InvalidOperationException
  • D. 输出 https://example.com/users,BaseAddress 缺少结尾斜杠时相对路径会替换最后一段
查看答案与解析

正确答案D

解析: 按 RFC 3986,相对引用 users 拼到没有结尾斜杠的 https://example.com/api 上,最后一段 api 被替换成 users。想要 api/users,BaseAddress 必须以 / 结尾。

关键点: BaseAddress 结尾斜杠决定拼接语义,是最常见的配置坑。

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

难度: 进阶

using var req = new HttpRequestMessage(HttpMethod.Post, "https://example.com");
req.Headers.Add("Content-Type", "application/json");
  • A. 正常添加,无异常
  • B. 抛 ArgumentException
  • C. 抛 InvalidOperationException,Content-Type 属于内容头,必须设置在 HttpContent 上
  • D. 静默忽略
查看答案与解析

正确答案C

解析: 请求头分三类:请求头、响应头、内容头。Content-Type 是内容头,只能放到 HttpContent.Headers;往 HttpRequestMessage.Headers 写会抛 InvalidOperationException(“Misused header name”)。

关键点: 内容头与请求头存放位置不同。

0830 相比默认选项,HttpCompletionOption.ResponseHeadersRead 的主要作用是什么?

难度: 实战

using var resp = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
using var stream = await resp.Content.ReadAsStreamAsync();
// 逐块读取并写入文件
  • A. GetAsync 收到响应头即返回,正文以流形式读取,避免整体缓冲大响应
  • B. 只返回响应头,不返回正文
  • C. 加快 DNS 解析
  • D. 自动对正文做 GZip 解压
查看答案与解析

正确答案A

解析: 默认 ResponseContentRead 会等正文整体读入内存才返回;ResponseHeadersRead 收到响应头就返回,正文通过 ReadAsStreamAsync 流式读取,适合大文件下载,避免内存暴涨。

关键点: 大响应要用 ResponseHeadersRead + 流式读取。

0831 HttpClient.Timeout 的默认值是多少?

难度: 实战

  • A. 30 秒
  • B. 100 秒
  • C. 无限
  • D. 10 秒
查看答案与解析

正确答案B

解析: HttpClient.Timeout 默认是 100 秒;只有显式设置为 Timeout.InfiniteTimeSpan 才是无限。很多团队以为默认是 30 秒或无限,导致超时行为与预期不符。

关键点: 默认 100 秒,不要凭印象猜。

0832 以下代码中,Timeout 是否约束第 4 行之后的流式读取?

难度: 进阶

using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
using var resp = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
using var stream = await resp.Content.ReadAsStreamAsync();
// 这里持续流式读取 30 秒
  • A. 读取到第 5 秒时抛 TaskCanceledException
  • B. 读取到第 5 秒时抛 TimeoutException
  • C. 读取到第 5 秒时连接被强制关闭
  • D. Timeout 不再约束该流式读取,可正常读完;需要自行用 CancellationToken 控制
查看答案与解析

正确答案D

解析: Timeout 作用于整个 SendAsync 调用:默认 ResponseContentRead 时覆盖到正文缓冲完成;改用 ResponseHeadersRead 后 SendAsync 在收到响应头时就返回,之后手动读取流不再受 HttpClient.Timeout 约束,必须自己传 CancellationToken 控制读超时。

关键点: 流式读取的超时由调用方接管。

0833 以下同步阻塞调用在哪些环境下有死锁风险?

难度: 进阶

var content = client.GetStringAsync(url).Result;
  • A. 任何环境都必然死锁
  • B. ASP.NET Core 下必然死锁
  • C. 在带 SynchronizationContext 的 UI 环境(WPF/WinForms)有死锁风险;ASP.NET Core 无同步上下文,表现为线程阻塞与线程池饥饿
  • D. 永远不会阻塞
查看答案与解析

正确答案C

解析: 在 WPF/WinForms 等带同步上下文的环境里,.Result 阻塞 UI 线程,而异步延续要回到该上下文执行,形成死锁;ASP.NET Core 没有同步上下文,不会死锁,但阻塞线程池线程,高并发下会线程池饥饿。正确做法是全程 async/await。

关键点: 同步阻塞的危害因环境而异,但都应避免。

0834 相比手写静态 HttpClient 单例,IHttpClientFactory 的主要收益是什么?

难度: 实战

  • A. 集中管理 HttpClient 与底层 Handler 的生命周期,避免每次新建造成套接字耗尽,并通过定期轮换 Handler 缓解 DNS 缓存陈旧问题
  • B. 自动实现超时重试与熔断
  • C. 自动加密请求
  • D. 让 HttpClient 变成线程不安全的
查看答案与解析

正确答案A

解析: IHttpClientFactory 的核心价值是管理 HttpClient/Handler 的生命周期与连接池:既有静态单例的连接复用,又通过周期轮换 Handler 让 DNS 变更生效;重试/熔断需要额外加 Polly 等策略,不是工厂自带的。

关键点: 工厂解决生命周期,不解决重试。

0835 响应体未读完就释放流,连接会发生什么?

难度: 进阶

using var client = new HttpClient();
using var resp = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
using var stream = await resp.Content.ReadAsStreamAsync();

var buffer = new byte[128];
_ = await stream.ReadAsync(buffer);
// 提前退出,未读完正文
  • A. 正常归还连接池,可复用
  • B. 抛异常
  • C. 内存泄漏,进程退出才释放
  • D. 连接无法归还连接池,会被关闭,下次请求需要新建连接
查看答案与解析

正确答案D

解析: 连接要回到池中复用的前提是响应体被完整读取;读到一半就释放,底层连接因“未消费完”被关闭而不是回池,下一个请求得重新建连。频繁这样做会显著增加连接建立开销。

关键点: 流式读取要么读完,要么接受连接关闭的代价。

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

难度: 实战

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

var resp = await client.GetAsync("https://example.com", cts.Token);
  • A. 照常发送请求
  • B. 抛 TaskCanceledException,请求在发送前即被取消
  • C. 抛 HttpRequestException
  • D. 返回 null
查看答案与解析

正确答案B

解析: 传入的 CancellationToken 在调用前已处于取消状态,SendAsync 会立即以取消收场,抛 TaskCanceledException,不会真的发出请求。

关键点: 已取消的 token 直接短路请求。

0837 读取响应流过程中取消,会发生什么?

难度: 进阶

using var resp = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
using var stream = await resp.Content.ReadAsStreamAsync();

var buffer = new byte[4096];
_ = await stream.ReadAsync(buffer, cts.Token);
// cts 在读取过程中被取消
  • A. 继续读取直到完成
  • B. 返回 0,静默结束
  • C. 抛 OperationCanceledException,响应流的异步读取支持取消
  • D. 抛 IOException
查看答案与解析

正确答案C

解析: 响应内容流的 ReadAsync 接受 CancellationToken,取消时抛 OperationCanceledException(其派生类型 TaskCanceledException),可用于中断大文件下载。

关键点: 不只是发送阶段,读取阶段也可以取消。

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

难度: 实战

var client = new HttpClient { Timeout = TimeSpan.Zero };
  • A. 抛 ArgumentOutOfRangeException,Timeout 必须为正数或 InfiniteTimeSpan
  • B. 正常创建,表示立即超时
  • C. 编译失败
  • D. 正常创建,自动回退到默认 100 秒
查看答案与解析

正确答案A

解析: HttpClient.Timeout 的 setter 拒绝小于等于零且不等于 InfiniteTimeSpan 的值,TimeSpan.Zero 会抛 ArgumentOutOfRangeException。想要“永不超时”必须显式用 Timeout.InfiniteTimeSpan

关键点: 零不是“立即超时”,而是非法值。

0839 关于同一 HttpClient 实例的并发使用,哪项正确?

难度: 进阶

using var client = new HttpClient();

var t1 = client.GetStringAsync(url);
var t2 = client.GetStringAsync(url);
await Task.WhenAll(t1, t2);
  • A. 不安全,必须为每个请求新建实例
  • B. HttpClient 实例方法设计为线程安全,可并发调用;但 DefaultRequestHeaders 的并发修改不安全
  • C. 所有成员都可在任意线程并发修改
  • D. 编译失败
查看答案与解析

正确答案B

解析: HttpClient 的实例方法(GetAsync/SendAsync 等)是线程安全的,同一个实例可以并发发起多个请求;但 DefaultRequestHeaders 不是线程安全的,不能在发起请求的同时并发修改。

关键点: “实例可并发用”不等于“所有成员都可并发改”。

0840 默认(ResponseContentRead)下,GetAsync 何时返回?

难度: 实战

  • A. 收到响应头立即返回
  • B. 建立 TCP 连接后返回
  • C. 服务器接受请求后返回
  • D. 响应正文完整读入内存后才返回,大响应会占用较多内存
查看答案与解析

正确答案D

解析: 默认的 HttpCompletionOption.ResponseContentRead 会先把整个响应正文缓冲到内存,SendAsync 才返回;因此大响应会占用较多内存,这也是要改用 ResponseHeadersRead 流式处理的原因。

关键点: 默认选项下“请求完成”=“正文也读完”。

官方资料

当前分类

C# 选择题

查看全部分类 →
  1. 40C# 试题 40:dynamic 与表达式树20 题
  2. 41C# 试题 41:System.Text.Json20 题
  3. 42C# 试题 42:HttpClient 与网络基础20 题
  4. 43C# 试题 43:比较、格式化与解析20 题
  5. 44C# 试题 44:C# 10~12 新特性20 题
ESC

输入关键词开始搜索