|
| 1 | +`Future` is inspired by [Clojure's](http://clojure.org/) [future](http://clojuredocs.org/clojure_core/clojure.core/future) function. A future represents a promise to complete an action at some time in the future. The action is atomic and permanent. The idea behind a future is to send an operation for asynchronous completion, do other stuff, then return and retrieve the result of the async operation at a later time. `Future`s run on the global thread pool. |
| 2 | + |
| 3 | +```cucumber |
| 4 | +Feature: |
| 5 | + As a highly responsive Ruby application |
| 6 | + I want long-running tasks on a separate thread |
| 7 | + So I can perform other tasks without waiting |
| 8 | +``` |
| 9 | + |
| 10 | +`Future`s have four possible states: *:unscheduled*, *:pending*, *:rejected*, and *:fulfilled*. When a `Future` is created it is set to *:unscheduled*. Once the `#execute` method is called the state becomes *:pending* and will remain in that state until processing is complete. A completed `Future` is either *:rejected*, indicating that an exception was thrown during processing, or *:fulfilled*, indicating success. If a `Future` is *:fulfilled* its `#value` will be updated to reflect the result of the operation. If *:rejected* the `reason` will be updated with a reference to the thrown exception. The predicate methods `#unscheduled?`, `#pending?`, `#rejected?`, and `#fulfilled?` can be called at any time to obtain the state of the `Future`, as can the `#state` method, which returns a symbol. |
| 11 | + |
| 12 | +Retrieving the value of a `Future` is done through the `#value` (alias: `#deref`) method. Obtaining the value of a `Future` is a potentially blocking operation. When a `Future` is *:rejected* a call to `#value` will return `nil` immediately. When a `Future` is *:fulfilled* a call to `#value` will immediately return the current value. When a `Future` is *:pending* a call to `#value` will block until the `Future` is either *:rejected* or *:fulfilled*. A *timeout* value can be passed to `#value` to limit how long the call will block. If `nil` the call will block indefinitely. If `0` the call will not block. Any other integer or float value will indicate the maximum number of seconds to block. |
| 13 | + |
| 14 | +The constructor can also be given zero or more processing options. Currently the only supported options are those recognized by the [Dereferenceable](Dereferenceable) module. |
| 15 | + |
| 16 | +The `Future` class also includes the behavior of the Ruby standard library [Observable](http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html) module, but does so in a thread-safe way. On fulfillment or rejection all observers will be notified according to the normal `Observable` behavior. The observer callback function will be called with three parameters: the `Time` of fulfillment/rejection, the final `value`, and the final `reason`. Observers added after fulfillment/rejection will still be notified as normal. The notification will occur on the global thread pool. |
| 17 | + |
| 18 | +### Examples |
| 19 | + |
| 20 | +A fulfilled example: |
| 21 | + |
| 22 | +```ruby |
| 23 | +require 'concurrent' |
| 24 | + |
| 25 | +count = Concurrent::Future.new{ sleep(10); 10 }.execute |
| 26 | +count.state #=> :pending |
| 27 | +count.pending? #=> true |
| 28 | + |
| 29 | +# do stuff... |
| 30 | + |
| 31 | +count.value(0) #=> nil (does not block) |
| 32 | + |
| 33 | +count.value #=> 10 (after blocking) |
| 34 | +count.state #=> :fulfilled |
| 35 | +count.fulfilled? #=> true |
| 36 | +count.value #=> 10 |
| 37 | +``` |
| 38 | + |
| 39 | +A rejected example: |
| 40 | + |
| 41 | +```ruby |
| 42 | +count = Concurrent::Future.execute{ sleep(10); raise StandardError.new("Boom!") } |
| 43 | +count.state #=> :pending |
| 44 | +count.pending? #=> true |
| 45 | + |
| 46 | +count.value #=> nil (after blocking) |
| 47 | +count.rejected? #=> true |
| 48 | +count.reason #=> #<StandardError: Boom!> |
| 49 | +``` |
| 50 | + |
| 51 | +An example with observation: |
| 52 | + |
| 53 | +```ruby |
| 54 | +class Ticker |
| 55 | + Stock = Struct.new(:symbol, :name, :exchange) |
| 56 | + |
| 57 | + def update(time, value, reason) |
| 58 | + ticker = value.collect do |symbol| |
| 59 | + Stock.new(symbol['symbol'], symbol['name'], symbol['exch']) |
| 60 | + end |
| 61 | + |
| 62 | + output = ticker.join("\n") |
| 63 | + print "#{output}\n" |
| 64 | + end |
| 65 | +end |
| 66 | + |
| 67 | +yahoo = Finance.new('YAHOO') |
| 68 | +future = Concurrent::Future.new { yahoo.update.suggested_symbols } |
| 69 | +future.add_observer(Ticker.new) |
| 70 | +future.execute |
| 71 | + |
| 72 | +# do important stuff... |
| 73 | + |
| 74 | +#>> #<struct Ticker::Stock symbol="YHOO", name="Yahoo! Inc.", exchange="NMS"> |
| 75 | +#>> #<struct Ticker::Stock symbol="YHO.DE", name="Yahoo! Inc.", exchange="GER"> |
| 76 | +#>> #<struct Ticker::Stock symbol="YAHOY", name="Yahoo Japan Corporation", exchange="PNK"> |
| 77 | +#>> #<struct Ticker::Stock symbol="YAHOF", name="YAHOO JAPAN CORP", exchange="PNK"> |
| 78 | +#>> #<struct Ticker::Stock symbol="YOJ.SG", name="YAHOO JAPAN", exchange="STU"> |
| 79 | +#>> #<struct Ticker::Stock symbol="YHO.SG", name="YAHOO", exchange="STU"> |
| 80 | +#>> #<struct Ticker::Stock symbol="YHOO.BA", name="Yahoo! Inc.", exchange="BUE"> |
| 81 | +#>> #<struct Ticker::Stock symbol="YHO.DU", name="YAHOO", exchange="DUS"> |
| 82 | +#>> #<struct Ticker::Stock symbol="YHO.HM", name="YAHOO", exchange="HAM"> |
| 83 | +#>> #<struct Ticker::Stock symbol="YHO.BE", name="YAHOO", exchange="BER"> |
| 84 | +``` |
0 commit comments