君子博学而日参省乎己 则知明而行无过矣

博客园 首页 新随笔 联系 订阅 管理

参考Wiki,Future,Promise或Delay是用于并发编程的一种同步构造。它们表示一个对象,这个对象用来作为一次计算的结果的代理,而该结果被初始化为未知,因为这个对象产生时计算还没有结束(或还没有开始)。

Future是对异步过程调用的结果的抽象,它并不关心具体的异步机制。这样无论是线程、网络、IO或者RPC,只要是异步过程调用,都可以通过Future的概念统一处理。此外提供基于Future的接口可以简化代码编写,以顺序的方式编写异步代码。

在.NET提供的异步编程模型(APM)中使用的IAsyncResult接口某种程度上就是Future的概念,在APM中,调用异步操作后立即获得一个IAsyncResult引用,之后可以通过阻塞、轮询或回调方法获取异步结果。举个例子:

 1 using System;
 2 using System.Threading;
 3 
 4 class Program
 5 {
 6     static string LongTask()
 7     {
 8         Thread.Sleep(5000);
 9         return "long_task_result";
10     }
11 
12     static void Main()
13     {
14         var asyncTask = new Func<string>(LongTask);
15 
16         Console.WriteLine("Start async task.");
17         var asyncResult = asyncTask.BeginInvoke(null, null);
18 
19         Console.WriteLine("Perform other tasks.");
20 
21         var result = asyncTask.EndInvoke(asyncResult);
22         Console.WriteLine("Obtained task result: " + result);
23     }
24 }

可以看到在使用IAsyncResult时完全不用关心任何线程问题,实际上在异步IO调用时不会占用任何线程。上面的代码在调用EndInvoke时会阻塞线程直到异步任务返回,通常在执行异步操作时并不希望发生阻塞,而是在异步操作结束后自动执行后续处理,对这个需求APM提供了回调方法:

 1 using System;
 2 using System.Threading;
 3 
 4 class Program
 5 {
 6     static string LongTask()
 7     {
 8         Thread.Sleep(5000);
 9         return "long_task_result";
10     }
11 
12     static void Main()
13     {
14         var asyncTask = new Func<string>(LongTask);
15 
16         Console.WriteLine("Start async task.");
17         var asyncResult = asyncTask.BeginInvoke(ar => {
18             var result = asyncTask.EndInvoke(ar);
19             Console.WriteLine("Obtained task result: " + result);
20         }, null);
21 
22         Console.WriteLine("Perform other tasks.");
23 
24         asyncResult.AsyncWaitHandle.WaitOne();
25     }
26 }

Future只表达了一个返回结果的异步过程调用,而完整的异步操作往往还包括对异步调用的结果进行后续处理的过程,这在APM以及.NET 2.0中新增的EAP模式中都是使用回调来完成的,实际上这个后续处理过程使用Continuation来表达非常合适。在.NET 4.0的任务并行库(TPL)中,由Task<T>类更加完整地实现了Future的概念,并结合了Continuation用于处理后续任务。现在上面的代码可以写成这样:

 1 using System;
 2 using System.Threading;
 3 using System.Threading.Tasks;
 4 
 5 class Program
 6 {
 7     static string LongTask()
 8     {
 9         Thread.Sleep(5000);
10         return "long_task_result";
11     }
12 
13     static void Main()
14     {
15         Console.WriteLine("Start async task.");
16         var asyncTask = Task<string>.Factory.StartNew(LongTask);
17 
18         var continuation = asyncTask.ContinueWith(task => {
19             Console.WriteLine("Obtained task result: " + task.Result);
20         });
21 
22         Console.WriteLine("Perform other tasks.");
23 
24         continuation.Wait();
25     }
26 }

这样整个流程写起来感觉“顺序”多了,C# 5提供的async和await关键字就是在此基础上由编译器负责生成Task和Continuation代码来为你自动封装这一切,可以几乎完全以顺序的方式编写异步代码。关于C# 5中新的异步编程方式,可以参考老赵(@jeffz_cn)的PDC 2010:C#与Visual Basic的未来系列文章。

Task可以在创建时就开始执行,或在之后调用Task.Start()或获取Task.Result时才开始执行,这称为Lazy Future。上面代码中的continuation也是一个Task,这样就可以把一系列的Task组合起来形成一个异步任务链,这称为Future pipeline或Call-stream。利用Future pipeline可以减少RPC中远程调用的次数从而提高效率。

在异步过程计算完成之后设置Future的值的过程称为绑定(Binding)或Resolving,一般情况下会在计算完成时或在调用获取值的方法(Task.Result)时自动执行绑定。某些Future实现出于权限考虑会对绑定做出限制,如C++11中的std::future提供的是一个只读视图(Read-only view),由对应的std::promise执行绑定;在Alice ML中,可以设置一个只能由特定线程进行绑定的Future。

在同步获取Future的值时也有多种设计选择:

  • 阻塞当前线程直到Future绑定完毕,通常会允许设置一个超时时间。
  • 不允许同步获取Future的值,试图获取时会得到一个错误信号,如抛出一个异常。
  • 如果Future已经绑定则获取成功,否则得到错误信号。这会引入不确定性和潜在的争用条件,通常不会这么设计。

目前支持并发编程的语言基本上都提供了Future的实现,如Java的java.util.concurrent.Future,.NET TPL中的Task,Python的pythonfutures,Ruby的Promise gem等,JavaScript也有多个提供了Future实现的库。

- See more at: http://ravenw.com/blog/2011/09/20/future-and-promise-in-concurrent-programming/#sthash.d0Bn6z32.dpuf

转载 http://ravenw.com/blog/2011/09/20/future-and-promise-in-concurrent-programming/

posted on 2013-11-03 16:14  刺猬的温驯  阅读(508)  评论(0编辑  收藏  举报