Async/Await网络请求示例疑问:如何实现异步下载与计算并行?
async/await 实际应用与异步任务调度问题
我完全搞不懂async/await的实际应用意义。作为初学者,我知道async/await多用于线程需要长时间等待的操作(比如数据库响应、网络请求、文件处理),而非长耗时计算;也看到资料说它用来避免阻塞主线程,但我疑惑为什么不用创建Task的方式来避免阻塞。
我写了一段包含网络下载和CPU密集型循环的代码,结果发现while(i < int.MaxValue)操作会先占满CPU资源,等它执行完后下载才开始,整个示例耗时约54秒。请问怎么让下载异步执行的同时,线程能处理while循环任务,避免资源闲置?
using System.Net; string PathProject = Directory.GetParent(Directory.GetCurrentDirectory()).Parent.Parent.Parent.FullName; //Create folder 1 in the project folder DirectoryInfo Path = new DirectoryInfo($"{PathProject}\\1"); int Iterations = Environment.ProcessorCount * 3; string file = "https://s182vla.storage.yandex.net/rdisk/82b08d86b9920a5e889c6947e4221eb1350374db8d799ee9161395f7195b0b0e/62f75403/geIEA69cusBRNOpxmtup5BdJ7AbRoezTJE9GH4TIzcUe-Cp7uoav-lLks4AknK2SfU_yxi16QmxiuZOGFm-hLQ==?uid=0&filename=004%20-%2002%20Lesnik.mp3&disposition=attachment&hash=e0E3gNC19eqNvFi1rXJjnP1y8SAS38sn5%2ByGEWhnzE5cwAGsEnlbazlMDWSjXpyvq/J6bpmRyOJonT3VoXnDag%3D%3D&limit=0&content_type=audio%2Fmpeg&owner_uid=160716081&fsize=3862987&hid=98984d857027117759bc5ce6092eaa6a&media_type=audio&tknv=v2&rtoken=k9xogU6296eg&force_default=no&ycrid=na-2bc914314062204f1cbf810798018afd-downloader16e&ts=5e61a6daac6c0&s=eef8b08190dc7b22befd6bad89e1393b394869a1668d9b8af3730cce4774e8ad&pb=U2FsdGVkX1__q3AvjJzgzWG4wVR80Oh8XMl-0Dlfyu9FhqAYQVVkoBV0dtBmajpmOkCXKUXPbREOS-MZCxMNu2rkAkKq_n-AXcZ85svtSFs"; List<Task> tasks = new List<Task>(); void MyMethod1(int i) { WebClient client = new WebClient(); client.DownloadFile(file, $"{Path}\\{i}.mp3"); } void MyMethod2() { int i = 0; while (i < int.MaxValue) { i++; } } DateTime dateTimeStart = DateTime.Now; for (int i = 0; i < Iterations; i++) { int j = i; tasks.Add(Task.Run(() => MyMethod1(j))); } for (int i = 0; i < Iterations; i++) { tasks.Add(Task.Run(() => { MyMethod2(); MyMethod2(); })); } Task.WaitAll(tasks.ToArray()); Console.WriteLine(DateTime.Now - dateTimeStart); while (true) { Thread.Sleep(100); if (Path.GetFiles().Length == Iterations) { Thread.Sleep(1000); foreach (FileInfo f in Path.GetFiles()) { f.Delete(); } return; } }
问题原因
你的代码出现下载等待CPU任务完成的核心原因:
WebClient.DownloadFile是同步IO方法,即便用Task.Run包裹,它也会持续占用线程池线程直到下载完成。- 你创建了
Environment.ProcessorCount * 3个CPU密集任务,这些任务会占满线程池的工作线程(线程池默认根据CPU核心数调整并发数),没有空闲线程处理下载任务,导致下载必须等CPU任务释放线程后才能启动。
解决方法
要让下载和CPU任务并行执行,核心是使用真正的异步IO方法配合async/await,这样IO等待时不会占用线程池线程,线程可被调度去处理CPU密集任务。
修改后的代码示例
using System.Net.Http; string PathProject = Directory.GetParent(Directory.GetCurrentDirectory()).Parent.Parent.Parent.FullName; DirectoryInfo Path = new DirectoryInfo($"{PathProject}\\1"); int Iterations = Environment.ProcessorCount * 3; string file = "https://s182vla.storage.yandex.net/rdisk/82b08d86b9920a5e889c6947e4221eb1350374db8d799ee9161395f7195b0b0e/62f75403/geIEA69cusBRNOpxmtup5BdJ7AbRoezTJE9GH4TIzcUe-Cp7uoav-lLks4AknK2SfU_yxi16QmxiuZOGFm-hLQ==?uid=0&filename=004%20-%2002%20Lesnik.mp3&disposition=attachment&hash=e0E3gNC19eqNvFi1rXJjnP1y8SAS38sn5%2ByGEWhnzE5cwAGsEnlbazlMDWSjXpyvq/J6bpmRyOJonT3VoXnDag%3D%3D&limit=0&content_type=audio%2Fmpeg&owner_uid=160716081&fsize=3862987&hid=98984d857027117759bc5ce6092eaa6a&media_type=audio&tknv=v2&rtoken=k9xogU6296eg&force_default=no&ycrid=na-2bc914314062204f1cbf810798018afd-downloader16e&ts=5e61a6daac6c0&s=eef8b08190dc7b22befd6bad89e1393b394869a1668d9b8af3730cce4774e8ad&pb=U2FsdGVkX1__q3AvjJzgzWG4wVR80Oh8XMl-0Dlfyu9FhqAYQVVkoBV0dtBmajpmOkCXKUXPbREOS-MZCxMNu2rkAkKq_n-AXcZ85svtSFs"; List<Task> tasks = new List<Task>(); // 异步下载方法 async Task MyAsyncDownload(int i) { using var httpClient = new HttpClient(); var fileBytes = await httpClient.GetByteArrayAsync(file); await File.WriteAllBytesAsync($"{Path}\\{i}.mp3", fileBytes); } void MyCpuIntensiveTask() { int i = 0; while (i < int.MaxValue) { i++; } } DateTime dateTimeStart = DateTime.Now; // 添加异步下载任务 for (int i = 0; i < Iterations; i++) { int j = i; tasks.Add(MyAsyncDownload(j)); } // 添加CPU密集任务 for (int i = 0; i < Iterations; i++) { tasks.Add(Task.Run(() => { MyCpuIntensiveTask(); MyCpuIntensiveTask(); })); } await Task.WhenAll(tasks); Console.WriteLine(DateTime.Now - dateTimeStart); // 后续清理逻辑 while (true) { await Task.Delay(100); if (Path.GetFiles().Length == Iterations) { await Task.Delay(1000); foreach (FileInfo f in Path.GetFiles()) { f.Delete(); } return; } }
async/await 的实际意义
- 线程高效利用:异步IO操作(如网络下载)等待响应时,
await会释放当前线程,让线程池将该线程调度去处理其他任务(比如你的CPU密集循环),避免线程在等待时闲置。 - 代码可读性:
async/await让异步代码的结构和同步代码几乎一致,比回调、手动管理Task的方式更易维护。 - 避免阻塞主线程:在UI应用中,主线程阻塞会导致界面卡死,
async/await能让主线程在等待IO时继续处理用户交互——这是Task.Run做不到的(Task.Run只是把任务扔到后台线程,主线程若调用Task.WaitAll仍会被阻塞)。
注意:Task.Run适合处理CPU密集型任务,可将计算任务放到后台线程避免阻塞主线程;但IO密集型任务用async/await配合异步API才是最优解,因为不需要占用线程等待IO响应。
内容的提问来源于stack exchange,提问作者user19747524
相关产品推荐
相关产品推荐

