Console.WriteLine对并行执行的影响及异步DNS解析实现差异问询
异步并行DNS解析的两种实现差异解析
我正在学习异步与并行编程,需对一组IP地址进行DNS解析,为此编写了如下方法:
private static Task<string> ResolveAsync(string ipAddress) { return Task.Run(() => Dns.GetHostEntry(ipAddress).HostName); }
现有两种并行解析实现方式:
第一种:统一启动任务,全部完成后打印结果
// 获取 orderedClientIps 逻辑省略 var taskArray = new List<Task>(); foreach (var orderedClientIp in orderedClientIps) { var task = new Task(async () => { orderedClientIp.Address = await ResolveAsync(orderedClientIp.Ip); }); taskArray.Add(task); task.Start(); } Task.WaitAll(taskArray.ToArray()); foreach (var orderedClientIp in orderedClientIps) { Console.WriteLine($"{(orderedClientIp.Ip)} ({orderedClientIp.Ip}) - {orderedClientIp.Count}"); }
第二种:任务完成后立即打印结果
foreach (var orderedClientIp in orderedClientIps) { var task = new Task(async () => { orderedClientIp.Address = await ResolveAsync(orderedClientIp.Ip); Console.WriteLine($"{(orderedClientIp.Ip)} ({orderedClientIp.Ip}) - {orderedClientIp.Count}"); }); taskArray.Add(task); task.Start(); } Task.WaitAll(taskArray.ToArray());
执行后发现:第一种方式会在全部解析完成后一次性打印结果,第二种则是逐个打印。我认为第一种方式的并行性更好,但不确定两种方式在异步并行编程中的具体差异,同时想了解第二种方式是否违背了Task.WaitAll的作用。
核心差异与解答
并行性无区别
两种方式的DNS解析环节是完全并行的——所有解析任务都是同时启动并在后台执行,不存在谁的并行性更好的说法。差异仅在于结果处理的时机和线程:- 第一种:解析任务全部完成后,由调用
WaitAll的线程(通常是主线程)统一遍历打印所有结果。 - 第二种:每个解析任务完成后,直接在该任务所在的线程池线程上立即打印结果,
WaitAll只是等待所有"解析+打印"的完整任务执行完毕。
- 第一种:解析任务全部完成后,由调用
第二种方式未违背Task.WaitAll的作用
Task.WaitAll的核心作用就是等待传入的所有Task对象执行完成,不管Task内部包含的是单一操作还是多步操作。第二种方式中,每个Task封装了解析和打印两个步骤,WaitAll等待的是所有这些完整任务结束,完全符合它的设计意图,没有违背。
代码优化建议
- 避免使用
new Task(async () => ...).Start()写法:异步lambda传入Task构造函数会被视为async void,导致Task无法追踪await之后的操作,也无法捕获异步异常。正确的写法是用Task.Run(async () => ...),它会返回一个代表整个异步流程的Task。 - 优化
ResolveAsync方法:Dns.GetHostEntry有原生异步方法GetHostEntryAsync,无需用Task.Run包装同步方法,更高效且符合异步编程规范:
private static async Task<string> ResolveAsync(string ipAddress) { var hostEntry = await Dns.GetHostEntryAsync(ipAddress); return hostEntry.HostName; }
内容的提问来源于stack exchange,提问作者skyhunter96
相关产品推荐
相关产品推荐

