.NET Core使用Task获取多页面内容长度结果错误,如何实现多线程并行?
问题原因分析
- 第一个写法结果错误的核心原因是循环变量捕获问题:C#中for循环的迭代变量i是整个循环作用域共享的,
Task.Run的lambda属于延迟执行,等到实际执行时大概率循环已经跑完,i已经变成了101,所有请求的url都是view/101,自然输出结果相同。 - 第二个写法串行的原因是每次循环都await了
Task.Run的结果,必须等上一个任务执行完才会进入下一次循环,所以只能串行执行。 - 额外优化提示:原代码中每次请求都实例化
HttpClient会导致端口耗尽问题,官方建议全局复用单例HttpClient,不影响作业要求的前提下可以优化。
正确实现(符合仅用Task的作业要求)
// 全局复用HttpClient,不影响作业逻辑的前提下可优化,也可以保留原方法的实例化逻辑 private static readonly HttpClient _httpClient = new HttpClient(); public static async Task GetContentLengthAsync(string url) { var httpResponse = await _httpClient.GetAsync(url); if (httpResponse.IsSuccessStatusCode) { var page = await httpResponse.Content.ReadAsStringAsync(); Console.WriteLine($"{url} 内容长度:{page.Length}"); } else { Console.WriteLine($"{url} 请求失败,状态码:{httpResponse.StatusCode}"); } } public static async Task GetLengthsAsync() { List<Task> allTasks = new List<Task>(); for (int i = 1; i <= 100; i++) { // 循环内定义局部变量,每次迭代生成独立的变量副本,避免闭包捕获共享的迭代变量i int currentPage = i; // 只将任务加入集合,不等待,直接进入下一次循环 allTasks.Add(Task.Run(() => GetContentLengthAsync($"https://www.website.com/view/{currentPage}"))); } // 统一等待所有任务执行完成 await Task.WhenAll(allTasks); }
实现说明
- 局部变量
currentPage解决了闭包捕获共享变量的问题,每个任务的url参数不会错乱,结果正确 - 所有任务会被线程池调度并行执行,充分利用多线程资源,不会出现串行的问题
- 整个方法是异步的,调用处
await GetLengthsAsync()不会阻塞主线程,控制台可以正常响应其他操作
内容的提问来源于stack exchange,提问作者Ceco Svidovski
相关产品推荐
相关产品推荐

