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

返回目录

Task.Unwrap基本使用

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

就像这样,不用Unwrap的话:

staticvoid Main(string[] args)
{
doo();
Task.Delay(-).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().Wait();
return"Mgen";
});
return task;
}); //第二个await后才会返回string
Console.WriteLine(await result);
}

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

staticasyncvoid doo()
{
//运行嵌套的Task
//Task返回Task<Task<string>>
//await后类型为Task<string>,Unwrap后result类型为string
var result =awaitTask.Run<Task<string>>(() =>
{
var task =Task.Run<string>(() =>
{
Task.Delay().Wait();
return"Mgen";
});
return task;
}).Unwrap(); //不需要await,result已经是string
Console.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(-).Wait();
} staticvoid doo()
{
//int数据
var ints =Enumerable.Range(, );
//转换并输出结果
foreach (var str in ints.Select(async i =>await Int2StringAsync(i)))
Console.WriteLine(str);
} //异步将int转换成string
staticasyncTask<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(, );
//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(, );
//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);

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

  1. 扩展方法和Enumerable

    .NET中扩展方法和Enumerable(System.Linq) LINQ是我最喜欢的功能之一,程序中到处是data.Where(x=x>5).Select(x)等等的代码,她使代码看起来更好 ...

  2. 【转载】C#中double.TryParse方法和double.Parse方法的异同之处

    在C#编程过程中,double.TryParse方法和double.Parse方法都可以将字符串string转换为double类型,但两者还是有区别,最重要的区别在于double.TryParse方法 ...

  3. 【转载】 C#中decimal.TryParse方法和decimal.Parse方法的异同之处

    在C#编程过程中,decimal.TryParse方法和decimal.Parse方法都可以将字符串string转换为decimal类型,但两者还是有区别,最重要的区别在于decimal.TryPar ...

  4. 【转载】C#中float.TryParse方法和float.Parse方法的异同之处

    在C#编程过程中,float.TryParse方法和float.Parse方法都可以将字符串string转换为单精度浮点类型float,但两者还是有区别,最重要的区别在于float.TryParse方 ...

  5. 【转载】C#中int.TryParse方法和int.Parse方法的异同之处

    在C#编程过程中,int.TryParse方法和int.Parse方法都可以将字符串string转换为整型int类型,但两者还是有区别,最重要的区别在于int.TryParse方法在字符串无法转换为i ...

  6. LINQ查询表达式详解(1)——基本语法、使用扩展方法和Lambda表达式简化LINQ查询

    简介 使用线程的主要原因:应用程序中一些操作需要消耗一定的时间,比如对文件.数据库.网络的访问等等,而我们不希望用户一直等待到操作结束,而是在此同时可以进行一些其他的操作.  这就可以使用线程来实现. ...

  7. 扩展方法和Lambda之练习手记

    扩展方法是我们日常开发当中所经常简化代码,提高性能和代码可读性的一个重要开发手段. 扩展方法是一个只能在静态类中声明的静态方法 Lambda 是一个表达式 ,学会了 可以使代码简洁,也是装13的利器. ...

  8. .NET中扩展方法和Enumerable(System.Linq)

    LINQ是我最喜欢的功能之一,程序中到处是data.Where(x=x>5).Select(x)等等的代码,她使代码看起来更好,更容易编写,使用起来也超级方便,foreach使循环更加容易,而不 ...

  9. ThinkPHP 中M方法和D方法详解----转载

    转载的地址,http://blog.163.com/litianyichuanqi@126/blog/static/115979441201223043452383/ 自己学到这里的时候,不能清除的分 ...

随机推荐

  1. TDBGridEh的 搜索面板 TDBGridSearchPanel

    TCustomDBGridEh.Create FSearchPanelControl := TDBGridSearchPanelControlEh.Create(Self); //这里,创建的构造函数 ...

  2. Swift语言中如何使用JSON数据教程

    这是一篇翻译文章,原文出处:http://www.raywenderlich.com/82706/working-with-json-in-swift-tutorial Swift语言中如何使用JSO ...

  3. JVM-class文件完全解析-方法表集合

    方法表集合 前面的魔数,次版本号,主板本号,常量池入口,常量池,访问标志,类索引,父类索引,接口索引集合,字段表集合,那么再接下来就是方法表了.   方法表的构造如同字段表一样,依次包括了访问标志(a ...

  4. java基础-007

    41.Servlet Servlet 是处理客户端请求并产生动态网页内容的Java类.Servlet主要是用来处理或者存储HTML表单提交的数据,产生动态内容,在无状态的HTTP协议下管理状态信息.所 ...

  5. 2013年7月份第4周51Aspx源码发布详情

    大型企业通用管理ERP源码  2013-7-26 [VS2010]2013.7.4更新内容:1.修复决策模式-客户等级不能保存问题.2.修复企业知识库有报错问题.3.修复运营模式-人力资源分析模块-在 ...

  6. uitabbarcontroller中 在设置tab bar item的image属性后不显示问题

    开始使用ios中的UITabBarController,在给Tab Bar Item设置自定义图片的时候,遇到了问题 按照如下配置: 出来的结果确是: 实际上test24.png应该是: 纠结了很久, ...

  7. hdu2795 线段树

    //Accepted 6396 KB 3046 ms //线段树 //由于n只有200000,我们可以知道,当h>200000时,大于200000的部分是没有用的 //所以我们可以用n来创建线段 ...

  8. C#操作txt问件,进行清空添加操作

    //把txt清空 FileStream stream = File.Open(Adr,FileMode.OpenOrCreate,FileAccess.Write); stream.Seek(, Se ...

  9. Python网络编程03----Python3.*中socketserver

    socketserver(在Python2.*中的是SocketServer模块)是标准库中一个高级别的模块.用于简化网络客户与服务器的实现(在前面使用socket的过程中,我们先设置了socket的 ...

  10. error: Apostrophe not preceded by \

    解决方案为:在编译出错提示中找到相关的string.xml文档,在string标签中的字符串含有单引号(')前面,加上反斜杠(\)转义即可.