Task.Run 和 Task.Factory.StartNew
2016-06-12 13:03
295 查看
在.Net 4中,
于是,在.NET Framework 4.5开发者预览版中,微软引进了新的
实际上等价于:
使用这些默认参数,
普通任务(
可取消任务(
同步委托(
前两个很明显,对于第一点如果是用的
第三点要更有趣一些,它直接关系到Visual studio 11中的C#和Visual Basic的异步语言支持。我们先使用
这里
此处的
这里使用了
为了应对这几种情况,在.Net 4中引入了
现在,变量
现在回到
如前所述,这是一个快捷方式。
上面讲的全部类容都意味着你可以使用
此处变量
有趣的是,新的
或者,可以使用第二个
这里的
Task.Factory.StartNew是启动一个新
Task的首选方法。它有很多重载方法,使它在具体使用当中可以非常灵活,通过设置可选参数,可以传递任意状态,取消任务继续执行,甚至控制任务的调度行为。所有这些能力也带来了复杂性的提升,你必须知道何时应该使用何种重载方法,提供哪种调度方式等等。并且
Task.Factory.StartNew这种写法也不够简洁明快,至少对它使用的主要场景不够快,一般它使用的主要场景只是将一个工作任务丢给一个后台线程执行而已。
于是,在.NET Framework 4.5开发者预览版中,微软引进了新的
Task.Run方法。新方法不是为了替代旧的
Task.Factory.StartNew方法,只是提供了一种使用
Task.Factory.StartNew方法的更简洁的形式,而不需要去指定那一系列参数。这是一个捷径,事实上,
Task.Run的内部实现逻辑跟
Task.Factory.StartNew一样,只是传递了一些默认参数。比如当你使用
Task.Run:
Task.Run(someAction);
实际上等价于:
Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
使用这些默认参数,
Task.Run就能用于大多数情况——只是将任务简单的交给后台线程池去执行(这也是使用
TaskScheduler.Default参数的目标)。这也并不意味着
Task.Factory.StartNew方法就不必再使用了,它仍然有很多重要的用处。你可以通过控制
TaskCreationOptions参数来控制任务的行为,你也可以通过控制
TaskScheduler来控制任务应该如何排队如何运行,你也可以使用重载方法中的接受对象状态那个参数,对于一些性能敏感的代码,它可以用于避免闭包以及相应的资源分配。不过对于上面那个简单的例子,
Task.Run是最友好。
Task.Run提供了八种重载方式,用于提供下面这几种组合情况:
普通任务(
Task)和带返回值任务(
Task<TResult>)
可取消任务(
Cancelable)和不可取消任务(
non-cancelabl)
同步委托(
Synchronous)和异步委托(
Asynchronous)
前两个很明显,对于第一点如果是用的
Task做返回值的重载方法,那么该任务就没有返回值,如果是用的
Task<TResult>做返回值的重载方法,那么该任务就有一个类型为
TResult的返回值。对于第二点,也有接受
CancellationToken参数的重载,可以在任务开始之前执行取消操作,然后并行任务(Task Parallel Library——TPL)就可以自然的过度到取消状态。
第三点要更有趣一些,它直接关系到Visual studio 11中的C#和Visual Basic的异步语言支持。我们先使用
Task.Factory.StartNew来展示下这个问题,如果有下面一段代码:
var t = Task.Factory.StartNew(() => { Task inner = Task.Factory.StartNew(() => {}); return inner; });
这里
t的类型会被推断为
Task<Task>,因为此处任务的委托类型是
Func<TResult>,所以这里
TResult的类型就是
Task,于是
StartNew方法就返回
Task<Task>,类似的,我可以改变成下面这种写法:
var t = Task.Factory.StartNew(() => { Task<int> inner = Task.Factory.StartNew(() => 42)); return inner; });
此处的
t的类型自然是
Task<Task<int>>,任务的委托类型还是
Func<TResult>,
TResult的类型就是
Task<int>,
StartNew方法就返回
Task<Task<int>>。这有什么关系呢?考虑下如果我们现在使用下面这种写法:
var t = Task.Factory.StartNew(async delegate { await Task.Delay(1000); return 42; });
这里使用了
async关键词,编译器会将这个委托映射成
Func<Task<int>>,调用这个委托最终会返回
Task<int>。因为这个这个委托是
Func<Task<int>>,
TResult的类型就是
Task<int>,所以最后
t的类型应该是
Task<Task<int>>,而不是
Task<int>。
为了应对这几种情况,在.Net 4中引入了
Unwrap方法。
Unwrap方法有两种重载形式,均是扩展方法的形式,一种是针对类型
Task<Task>,另一种是针对
<Task<TResult>>。微软只所以要把这个方法命名为解包(Unwrap),是因为这个方法可以返回任务的实际结果。对
Task<Task>调用
Unwrap方法可以返回一个新的
Task(就像内部任务的一个代理一样)代表它的内部任务。相似的,对
Task<Task<TResult>>调用
Unwrap返回一个新的
Task<TResult>代表它的内部任务。但是,如果外部任务失败了或者取消了,就不会有内部任务了,因为没有任务运行完成,所以代理任务也就变成了外部任务的状态。回到前面的例子,如果想让
t代表内部任务的返回值(在这个例子中,这个值是42),那么应该像下面这样写:
var t = Task.Factory.StartNew(async delegate { await Task.Delay(1000); return 42; }).Unwrap();
现在,变量
t的类型是
Task<int>,代表异步调用的结果。
现在回到
Task.Run,因为微软想让开发者尽可能的使用这个方法来启用后台任务,并且可以配合
async/await使用,所以微软决定在
Task.Run方法中内建
unwrapping的功能。这也是上面第三点所指的内容,
Task.Run的重载方法中有可以接受
Action(没有返回值的任务)的,有接受
Func<TResult>(返回
TResult的任务)的,有接受
Func<Task>(返回一个异步任务的任务)的,还有接受
Func<Task<TResult>>(返回一个带
TResult类型返回值的异步任务的任务)的。总的来说,
Task.Run方法提供了上面
Task.Factory.StartNew方法相同的
unwrapping操作。于是,我们可以这样写:
var t = Task.Run(async delegate { await Task.Delay(1000); return 42; });
t的类型是
Task<int>,此处
Task.Run执行的重载方法等价于:
var t = Task.Factory.StartNew(async delegate { await Task.Delay(1000); return 42; }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap();
如前所述,这是一个快捷方式。
上面讲的全部类容都意味着你可以使用
Task.Run调用标准的
lambdas/anonymous方法或是异步
lambdas/anonymous方法,最后总会按你所期望的行为运行。如果我们想让任务在后台运行并且想等待它的结果,那么可以像下面这样写:
int result = await Task.Run(async () => { await Task.Delay(1000); return 42; });
此处变量
result的类型正是你所期望的
int,并且在该任务被调用大约1秒钟后,变量
result的值被设置为42。
有趣的是,新的
await关键字被认为是等价于
Unwrap方法的一种新语法形式。于是,如果我们回到上面那个
Task.Factory.StartNew例子,我们可以先用
Unwrap重写上面那个代码片段:
int result = await Task.Factory.StartNew(async delegate { await Task.Delay(1000); return 42; }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap();
或者,可以使用第二个
await替换
Unwrap:
int result = await await Task.Factory.StartNew(async delegate { await Task.Delay(1000); return 42; }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
这里的
await await虽然看着别扭,但是并没有问题。
Task.Factory.StartNew方法返回一个
Task<Task<int>>,对
Task<Task<int>>使用
await实际上返回
Task<int>,然后再对
Task<int>使用
await最后返回
int,难道不是这样吗?
相关文章推荐
- oracle 如何搜索当前用户下所有表里含某个值的字段?(转)
- 剑指offer(20)-二叉搜索树的后序遍历序列
- 解决IllegalStateException: Can not perform this action after onSaveInstanceState
- 赞赏——人人都能赞赏成书
- 第十四周工作总结
- 第二阶段冲刺第七天
- Maple 教程 何青,科学出版社
- 离屏渲染学习笔记
- 十年坚持与奋斗,独著4本计算机著作终获中科院、清华、北大等一流大学收藏...
- 3、AngularJs的双向数据绑定
- 十年坚持与奋斗,独著4本计算机著作终获中科院、清华、北大等一流大学收藏
- BBC英语-unit5
- jump Consistent hash:零内存消耗,均匀,快速,简洁,来自Google的一致性哈希算法
- 解决Android Studio下Element layer-list must be declared问题
- 由索引节点(inode)爆满引发的问题
- 环形缓存之一致性 hash 算法( consistent hashing )
- 第十六周--阅读程序
- DecimalFormat类的使用,用于格式化十进制数字
- poj 1496 Word Index 计数/组合数学
- FindJpg(4)-列表的卡顿优化和RecyclerView的简单使用