1 Func<int, Task<int>> func = async x => 2 { 3 Console.WriteLine("starting x={0}", x); 4 await Task.Delay(x); 5 Console.WriteLine("ending x={0}", x); 6 return x * 2; 7 }; 8 9 Task<int> first1 = func(5); 10 Task<int> first2 = func(3); 11 Console.WriteLine("first1 result = {0}", first1.Result); 12 Console.WriteLine("first2 result = {0}", first2.Result); 13 /* starting x=5 14 starting x=3 15 ending x=3 16 ending x=5 17 first1 result = 10 18 first2 result = 6 */
此处我故意选择这样的值,以便让第二个操作早于第一个完成。但由于我们要在等待第一个 操作完成后再打印结果(使用 Result 属性,这将阻塞线程直到任务结束。再次强调一遍,运行 这样的代码时要十分谨慎!)
将异步代码放到异步方法中,也可得到同样的结果。 异步匿名函数并不会让我感到特别兴奋,但它也有自己的用途。尽管不能应用于LINQ查询 表达式,但在某些情况下,还是可以实现数据转换的异步执行的。这时,只需以一种略微不同的 方式思考整个过程即可。 在讨论完成分后,我们还会回到这个话题,但首先我想向大家展示一下异步匿名函数特别有 用的一个方面。我之前承诺过,要展示另一种在异步方法开始时及早执行参数验证的方式。你可 能还记得,在进入主操作前,需检查参数值是否为空。代码清单15-10是一个单个方法,其结果 与代码清单15-6中两个分离方法得到的结果完全相同。
1 static Task<int> ComputeLengthAsync(string text) 2 { 3 if (text == null) 4 { 5 throw new ArgumentNullException("text"); 6 } 7 Func<Task<int>> func = async () => 8 { 9 await Task.Delay(500); 10 return text.Length; 11 }; 12 return func(); 13 }
你会发现这并不是一个异步方法。如果是的话,异常会被包装到任务里,而不是立即抛出。 但我们还是想返回一个任务,因此在验证 之后,将工作包装到一个异步匿名函数中 ,调用委 托 并返回结果。 尽管这看上去还是有点丑,但比分割成两个方法要清晰多了。不过性能上会蒙受一点损失: 额外的包装会产生额外的代价。这在大多数情况下都没有问题,但如果你编写的库会应用于注重 性能的程序,则应在真实场景中检测成本,然后再决定使用哪种方法。