|
| 1 | +### Simple asynchronous task |
| 2 | + |
| 3 | +future = Concurrent.future { sleep 0.1; 1 + 1 } # evaluation starts immediately |
| 4 | + # => <#Concurrent::Edge::Future:0x7fa08385da60 pending blocks:[]> |
| 5 | +future.completed? # => false |
| 6 | +# block until evaluated |
| 7 | +future.value # => 2 |
| 8 | +future.completed? # => true |
| 9 | + |
| 10 | + |
| 11 | +### Failing asynchronous task |
| 12 | + |
| 13 | +future = Concurrent.future { raise 'Boom' } |
| 14 | + # => <#Concurrent::Edge::Future:0x7fa083834638 failed blocks:[]> |
| 15 | +future.value # => nil |
| 16 | +future.value! rescue $! # => #<RuntimeError: Boom> |
| 17 | +future.reason # => #<RuntimeError: Boom> |
| 18 | +# re-raising |
| 19 | +raise future rescue $! # => #<RuntimeError: Boom> |
| 20 | + |
| 21 | + |
| 22 | +### Chaining |
| 23 | + |
| 24 | +head = Concurrent.future { 1 } |
| 25 | +branch1 = head.then(&:succ) |
| 26 | +branch2 = head.then(&:succ).then(&:succ) |
| 27 | +branch1.zip(branch2).value # => [2, 3] |
| 28 | +(branch1 & branch2).then { |(a, b)| a + b }.value |
| 29 | + # => 5 |
| 30 | +# pick only first completed |
| 31 | +(branch1 | branch2).value # => 2 |
| 32 | + |
| 33 | + |
| 34 | +### Error handling |
| 35 | + |
| 36 | +Concurrent.future { Object.new }.then(&:succ).then(&:succ).rescue { |e| e.class }.value # error propagates |
| 37 | + # => NoMethodError |
| 38 | +Concurrent.future { Object.new }.then(&:succ).rescue { 1 }.then(&:succ).value |
| 39 | + # => 2 |
| 40 | +Concurrent.future { 1 }.then(&:succ).rescue { |e| e.message }.then(&:succ).value |
| 41 | + # => 3 |
| 42 | + |
| 43 | + |
| 44 | +### Delay |
| 45 | + |
| 46 | +# will not evaluate until asked by #value or other method requiring completion |
| 47 | +scheduledfuture = Concurrent.delay { 'lazy' } |
| 48 | + # => <#Concurrent::Edge::Future:0x7fa0831917b8 pending blocks:[]> |
| 49 | +sleep 0.1 |
| 50 | +future.completed? # => true |
| 51 | +future.value # => nil |
| 52 | + |
| 53 | +# propagates trough chain allowing whole or partial lazy chains |
| 54 | + |
| 55 | +head = Concurrent.delay { 1 } |
| 56 | + # => <#Concurrent::Edge::Future:0x7fa083172ef8 pending blocks:[]> |
| 57 | +branch1 = head.then(&:succ) |
| 58 | + # => <#Concurrent::Edge::Future:0x7fa083171c88 pending blocks:[]> |
| 59 | +branch2 = head.delay.then(&:succ) |
| 60 | + # => <#Concurrent::Edge::Future:0x7fa08294f528 pending blocks:[]> |
| 61 | +join = branch1 & branch2 |
| 62 | + # => <#Concurrent::Edge::Future:0x7fa08294e218 pending blocks:[]> |
| 63 | + |
| 64 | +sleep 0.1 # nothing will complete # => 0 |
| 65 | +[head, branch1, branch2, join].map(&:completed?) # => [false, false, false, false] |
| 66 | + |
| 67 | +branch1.value # => 2 |
| 68 | +sleep 0.1 # forces only head to complete, branch 2 stays incomplete |
| 69 | + # => 0 |
| 70 | +[head, branch1, branch2, join].map(&:completed?) # => [true, true, false, false] |
| 71 | + |
| 72 | +join.value # => [2, 2] |
| 73 | + |
| 74 | + |
| 75 | +### Flatting |
| 76 | + |
| 77 | +Concurrent.future { Concurrent.future { 1+1 } }.flat.value # waits for inner future |
| 78 | + # => 2 |
| 79 | + |
| 80 | +# more complicated example |
| 81 | +Concurrent.future { Concurrent.future { Concurrent.future { 1 + 1 } } }. |
| 82 | + flat(1). |
| 83 | + then { |f| f.then(&:succ) }. |
| 84 | + flat(1).value # => 3 |
| 85 | + |
| 86 | + |
| 87 | +### Schedule |
| 88 | + |
| 89 | +scheduled = Concurrent.schedule(0.1) { 1 } |
| 90 | + # => <#Concurrent::Edge::Future:0x7fa08224edf0 pending blocks:[]> |
| 91 | + |
| 92 | +scheduled.completed? # => false |
| 93 | +scheduled.value # available after 0.1sec # => 1 |
| 94 | + |
| 95 | +# and in chain |
| 96 | +scheduled = Concurrent.delay { 1 }.schedule(0.1).then(&:succ) |
| 97 | + # => <#Concurrent::Edge::Future:0x7fa0831f3d50 pending blocks:[]> |
| 98 | +# will not be scheduled until value is requested |
| 99 | +sleep 0.1 |
| 100 | +scheduled.value # returns after another 0.1sec # => 2 |
| 101 | + |
| 102 | + |
| 103 | +### Completable Future and Event |
| 104 | + |
| 105 | +future = Concurrent.future |
| 106 | + # => <#Concurrent::Edge::CompletableFuture:0x7fa0831e8090 pending blocks:[]> |
| 107 | +event = Concurrent.event |
| 108 | + # => <#Concurrent::Edge::CompletableEvent:0x7fa0831dae68 pending blocks:[]> |
| 109 | + |
| 110 | +# will be blocked until completed |
| 111 | +t1 = Thread.new { future.value } |
| 112 | +t2 = Thread.new { event.wait } |
| 113 | + |
| 114 | +future.success 1 |
| 115 | + # => <#Concurrent::Edge::CompletableFuture:0x7fa0831e8090 success blocks:[]> |
| 116 | +future.success 1 rescue $! |
| 117 | + # => #<Concurrent::MultipleAssignmentError: multiple assignment> |
| 118 | +future.try_success 2 # => false |
| 119 | +event.complete |
| 120 | + # => <#Concurrent::Edge::CompletableEvent:0x7fa0831dae68 completed blocks:[]> |
| 121 | + |
| 122 | +[t1, t2].each &:join |
| 123 | + |
| 124 | + |
| 125 | +### Callbacks |
| 126 | + |
| 127 | +queue = Queue.new # => #<Thread::Queue:0x007fa0831bac30> |
| 128 | +future = Concurrent.delay { 1 + 1 } |
| 129 | + # => <#Concurrent::Edge::Future:0x7fa0831b96c8 pending blocks:[]> |
| 130 | + |
| 131 | +future.on_success { queue << 1 } # evaluated asynchronously |
| 132 | + # => <#Concurrent::Edge::Future:0x7fa0831b96c8 pending blocks:[]> |
| 133 | +future.on_success! { queue << 2 } # evaluated on completing thread |
| 134 | + # => <#Concurrent::Edge::Future:0x7fa0831b96c8 pending blocks:[]> |
| 135 | + |
| 136 | +queue.empty? # => true |
| 137 | +future.value # => 2 |
| 138 | +queue.pop # => 2 |
| 139 | +queue.pop # => 1 |
| 140 | + |
| 141 | + |
| 142 | +### Thread-pools |
| 143 | + |
| 144 | +Concurrent.future(:fast) { 2 }.then(:io) { File.read __FILE__ }.wait |
| 145 | + # => <#Concurrent::Edge::Future:0x7fa08318b070 success blocks:[]> |
| 146 | + |
| 147 | + |
| 148 | +### Interoperability with actors |
| 149 | + |
| 150 | +actor = Concurrent::Actor::Utils::AdHoc.spawn :square do |
| 151 | + -> v { v ** 2 } |
| 152 | +end |
| 153 | + # => #<Concurrent::Actor::Reference /square (Concurrent::Actor::Utils::AdHoc)> |
| 154 | + |
| 155 | +Concurrent. |
| 156 | + future { 2 }. |
| 157 | + then_ask(actor). |
| 158 | + then { |v| v + 2 }. |
| 159 | + value # => 6 |
| 160 | + |
| 161 | +actor.ask(2).then(&:succ).value # => 5 |
| 162 | + |
| 163 | + |
| 164 | +### Common use-cases Examples |
| 165 | + |
| 166 | +# simple background processing |
| 167 | +Concurrent.future { do_stuff } |
| 168 | + # => <#Concurrent::Edge::Future:0x7fa0839ee8e8 pending blocks:[]> |
| 169 | + |
| 170 | +# parallel background processing |
| 171 | +jobs = 10.times.map { |i| Concurrent.future { i } } |
| 172 | +Concurrent.zip(*jobs).value # => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] |
| 173 | + |
| 174 | + |
| 175 | +# periodic task |
| 176 | +def schedule_job |
| 177 | + Concurrent.schedule(1) { do_stuff }. |
| 178 | + rescue { |e| report_error e }. |
| 179 | + then { schedule_job } |
| 180 | +end # => :schedule_job |
| 181 | + |
| 182 | +schedule_job |
| 183 | + # => <#Concurrent::Edge::Future:0x7fa082904f78 pending blocks:[]> |
| 184 | + |
| 185 | + |
0 commit comments