【转载】.NET(C#): Task.Unwrap扩展方法和async Lambda

.NET(C#): Task.Unwrap扩展方法和async Lambda

目录

  • Task.Unwrap基本使用
  • Task.Factory.StartNew和Task.Run的Unwrap操作
  • 使用案例:LINQ中的async Lambda

返回目录

Task.Unwrap基本使用

这个扩展方法定义在TaskExtensions类型中,命名空间在System.Threading.Tasks。Unwrap会把嵌套的Task<Task>或者Task<Task<T>>的结果提取出来。

就像这样,不用Unwrap的话:

staticvoid Main(string[] args){    doo();Task.Delay(-1).Wait();}

staticasyncvoid doo(){//运行嵌套的Task//Task返回Task<Task<string>>//第一个await后result类型为Task<string>var result =awaitTask.Run<Task<string>>(() =>        {var task =Task.Run<string>(() =>                {Task.Delay(1000).Wait();return"Mgen";                });return task;        });

//第二个await后才会返回stringConsole.WriteLine(await result);}

使用Unwrap后,结果可以直接从嵌套Task中提取出来:

staticasyncvoid doo(){//运行嵌套的Task//Task返回Task<Task<string>>//await后类型为Task<string>,Unwrap后result类型为stringvar result =awaitTask.Run<Task<string>>(() =>        {var task =Task.Run<string>(() =>                {Task.Delay(1000).Wait();return"Mgen";                });return task;        }).Unwrap();

//不需要await,result已经是stringConsole.WriteLine(result);}

返回目录

Task.Factory.StartNew和Task.Run的Unwrap操作

简单地讲,Task.Factory.StartNew和Task.Run区别之一就有Task.Run会自动执行Unwrap操作,但是Task.Factory.StartNew不会,Task.Run就是Task.Factory.StartNew的更人性化封装,而Task.Factory.StartNew则是原始的执行。(另外关于更多的区别,推荐PFX Team的一篇非常给力的文章:Task.Run vs Task.Factory.StartNew)。

通过代码来验证:

var task1 =Task.Factory.StartNew(async () =>"Mgen");var task2 =Task.Run(async () =>"Mgen");

Console.WriteLine(task1.GetType());Console.WriteLine(task2.GetType());

输出:

System.Threading.Tasks.Task`1[System.Threading.Tasks.Task`1[System.String]]System.Threading.Tasks.UnwrapPromise`1[System.String]

可以看到

使用Task.Factory.StartNew会返回原始的Task<Task<string>>。但是Task.Run则会直接返回async Lambda的结果,中间的Unwrap操作会自动进行。

返回目录

使用案例:LINQ中的async Lambda

文章讲述到这里,或许读者在想上述情况会不会很少见?不,任何使用async Lambda的情况都可能会出现上述情况,比如最近在搞一个WinRT的项目,使用LINQ去转换一些数据,但是许多WinRT的API只有异步执行的,这类问题就会出现,下方示例:

我们来进行一个再简单不过的LINQ Select操作,把一堆int转换成string,只不过转换过程是异步的,来看代码:

staticvoid Main(string[] args){    doo();Task.Delay(-1).Wait();}

staticvoid doo(){//int数据var ints =Enumerable.Range(1, 10);//转换并输出结果foreach (var str in ints.Select(async i =>await Int2StringAsync(i)))Console.WriteLine(str);}

//异步将int转换成stringstaticasyncTask<string> Int2StringAsync(int i){returnawaitTask.Run<string>(() => i.ToString());}

上面代码正确吗?很多人会认为没问题的,Select方法通过一个async Lambda调用异步转换方法,并使用await异步等待结果,那么async Lambda返回string,str类型也是string,最后输出所以字符串。

但事实上程序运行后会输出:

System.Threading.Tasks.Task`1[System.String]System.Threading.Tasks.Task`1[System.String]System.Threading.Tasks.Task`1[System.String]System.Threading.Tasks.Task`1[System.String]System.Threading.Tasks.Task`1[System.String]System.Threading.Tasks.Task`1[System.String]System.Threading.Tasks.Task`1[System.String]System.Threading.Tasks.Task`1[System.String]System.Threading.Tasks.Task`1[System.String]System.Threading.Tasks.Task`1[System.String]

str变量根本不是string,而是Task<string>。上边的推断错在(上面黄字标注的)“async Lambda返回string”,async Lambda的结果并没有被await,上面的await仅仅是对Int2StringAsync方法的异步等待,而async Lambda本身仍然会返回Task<string>,所以Select会返回一些列的Task<string>。

最简单的解决方案是,在处理结果的时候加上await,如下:

//str的类型事实上是:Task<string>Console.WriteLine(str);

这是最好的方法(如果能这样的话),因为有了这个await,事实上整个转换过程就异步化了!

当然如果你想在LINQ Select中直接返回结果string而不是Task<string>:

那么还有一种解决方案就是不使用async Lambda,就不存在嵌套Task的问题,直接在Select中返回异步方法的Task的Result属性:

//int数据var ints =Enumerable.Range(1, 10);//Select调用异步方法IEnumerable<string> strs = ints.Select(i => Int2StringAsync(i).Result);

如果一定要使用async Lambda,则必须将嵌套的Task进行Unwrap。(当然这里更多的是为了讨论技术本身,实际工作中没必要这么钻牛角尖呵呵。)

结合上面讲到的知识,使用Task.Factory.StartNew需要进行一个Unwrap,然后返回Task<T>的结果作为Select方法的最终返回值,代码:

//int数据var ints =Enumerable.Range(1, 10);//Select调用异步方法IEnumerable<string> strs = ints.Select(i =>Task.Factory.StartNew(async () =>await Int2StringAsync(i)).Unwrap().Result);

而Task.Run的话,不需要Unwrap:

IEnumerable<string> strs = ints.Select(i =>Task.Run(async () =>await Int2StringAsync(i)).Result);

转自http://www.mgenware.com/blog/?p=338#_hContent

时间: 2024-10-31 02:38:29

【转载】.NET(C#): Task.Unwrap扩展方法和async Lambda的相关文章

扩展方法和Enumerable

.NET中扩展方法和Enumerable(System.Linq) LINQ是我最喜欢的功能之一,程序中到处是data.Where(x=x>5).Select(x)等等的代码,她使代码看起来更好,更容易编写,使用起来也超级方便,foreach使循环更加容易,而不用for int..,linq用起来那么爽,那么linq内部是如何实现的?我们如何自定义linq?我们这里说的linq不是from score in scores  where score > 80 select score;而是Sys

wait方法和sleep方法的区别

一.概念.原理.区别 Java中的多线程是一种抢占式的机制而不是分时机制.线程主要有以下几种状态:可运行,运行,阻塞,死亡.抢占式机制指的是有多个线程处于可运行状态,但是只有一个线程在运行. 当有多个线程访问共享数据的时候,就需要对线程进行同步.线程中的几个主要方法的比较: Thread类的方法:sleep(),yield()等 Object的方法:wait()和notify()等 每个对象都有一个机锁来控制同步访问.Synchronized关键字可以和对象的机锁交互,来实现线程的同步. 由于s

jQuery.extend()方法和jQuery.fn.extend()方法

jQuery.extend()方法和jQuery.fn.extend()方法源码分析 这两个方法用的是相同的代码,一个用于给jQuery对象或者普通对象合并属性和方法一个是针对jQuery对象的实例,对于基本用法举几个例子: html代码如下: <!doctype html> <html> <head> <title></title> <script src='jquery-1.7.1.js'></script> <

[ jquery 效果 show([speed,[easing],[fn]]) hide([speed,[easing],[fn]]) ] 此方法用于显示隐藏的被选元素:show() 适用于通过 jQuery 方法和 CSS 中 display:none type=&#39;hidden&#39; 隐藏的元素(不适用于通过 visibility:hidden 隐藏的元素)

show()显示隐藏的被选元素:show() 适用于通过 jQuery 方法和 CSS 中 display:none type='hidden' 隐藏的元素(不适用于通过 visibility:hidden 隐藏的元素): hide() 方法隐藏被选元素: 参数 描述 speed 可选.规定显示效果的速度. 可能的值: 毫秒 "slow" "fast" easing 可选.规定在动画的不同点上元素的速度.默认值为 "swing". 可能的值: &

(转)C#中的委托,匿名方法和Lambda表达式

简介 在.NET中,委托,匿名方法和Lambda表达式很容易发生混淆.我想下面的代码能证实这点.下面哪一个First会被编译?哪一个会返回我们需要的结果?即Customer.ID=5.答案是6个First不仅被编译,并都获得正确答案,且他们的结果一样.如果你对此感到困惑,那么请继续看这篇文章. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Customer {     public int ID { get; set; }     p

ASP.NET Core 中文文档 第二章 指南(4.10)检查自动生成的Detail方法和Delete方

原文 Examining the Details and Delete methods 作者 Rick Anderson 翻译 谢炀(Kiler) 校对 许登洋(Seay).姚阿勇(Mr.Yao) 打开 Movie 控制器并查看 Details 方法: // GET: Movies/Details/5 public async Task<IActionResult> Details(int? id) { if (id == null) { return NotFound(); } var mo

Html.Partial方法和Html.RenderPartial方法

分布视图 PartialView 一般是功能相对独立的,类似用户控件的视图代码片段,可以被多个视图引用,引用方式如下. 1,Html.Partial方法和Html.RenderPartial方法 静态类System.Web.Mvc.Html.PartialExtensions,利用扩展方法,扩展了System.Web.Mvc.HtmlHelper,因此有了Html.Partial方法,方法返回值为MvcHtmlString 静态类System.Web.Mvc.Html.RenderPartial

Spark Rdd coalesce()方法和repartition()方法

在Spark的Rdd中,Rdd是分区的. 有时候需要重新设置Rdd的分区数量,比如Rdd的分区中,Rdd分区比较多,但是每个Rdd的数据量比较小,需要设置一个比较合理的分区.或者需要把Rdd的分区数量调大.还有就是通过设置一个Rdd的分区来达到设置生成的文件的数量. 有两种方法是可以重设Rdd的分区:分别是 coalesce()方法和repartition(). 这两个方法有什么区别,看看源码就知道了: def coalesce(numPartitions: Int, shuffle: Bool

Win8系统语音识别使用方法和xp内置语音输入软件安装

下面介绍两款现在主流系统的一些特殊功能,语音输入,也许你还没有正式使用这些功能,但是系统中既然有这项功能就有着它存在的意义,这节就讲下Win8与XP的语音识别功能的使用方法. 之一<Win8>启动语音识别功能 首先,用户需要准备一部笔记本电脑和一个麦克风.Win8语音识别程序能够支持任何类型的麦克风,甚至包括内置在用户笔记本中的扩音器.不过,微软表示,价格在20美元左右的麦克风效果最佳. 激活语音识别功能最简单方法就是打开"开始"(Start)界面,输入"语音&q