4
4
5
5
module Concurrent
6
6
7
- # {include:file:doc/scheduled_task.md}
7
+ # `ScheduledTask` is a close relative of `Concurrent::Future` but with one
8
+ # important difference. A `Future` is set to execute as soon as possible
9
+ # whereas a `ScheduledTask` is set to execute at a specific time. This
10
+ # implementation is loosely based on Java's
11
+ # [ScheduledExecutorService](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledExecutorService.html).
12
+ #
13
+ # The *intended* schedule time of task execution is set on object construction
14
+ # with first argument, `delay`. The delay is a numeric (floating point or integer)
15
+ # representing a number of seconds in the future. Any other value or a numeric
16
+ # equal to or less than zero will result in an exception. The *actual* schedule
17
+ # time of task execution is set when the `execute` method is called.
18
+ #
19
+ # The constructor can also be given zero or more processing options. Currently
20
+ # the only supported options are those recognized by the
21
+ # [Dereferenceable](Dereferenceable) module.
22
+ #
23
+ # The final constructor argument is a block representing the task to be performed.
24
+ # If no block is given an `ArgumentError` will be raised.
25
+ #
26
+ # **States**
27
+ #
28
+ # `ScheduledTask` mixes in the [Obligation](Obligation) module thus giving it
29
+ # "future" behavior. This includes the expected lifecycle states. `ScheduledTask`
30
+ # has one additional state, however. While the task (block) is being executed the
31
+ # state of the object will be `:in_progress`. This additional state is necessary
32
+ # because it has implications for task cancellation.
33
+ #
34
+ # **Cancellation**
35
+ #
36
+ # A `:pending` task can be cancelled using the `#cancel` method. A task in any
37
+ # other state, including `:in_progress`, cannot be cancelled. The `#cancel`
38
+ # method returns a boolean indicating the success of the cancellation attempt.
39
+ # A cancelled `ScheduledTask` cannot be restarted. It is immutable.
40
+ #
41
+ # **Obligation and Observation**
42
+ #
43
+ # The result of a `ScheduledTask` can be obtained either synchronously or
44
+ # asynchronously. `ScheduledTask` mixes in both the [Obligation](Obligation)
45
+ # module and the
46
+ # [Observable](http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html)
47
+ # module from the Ruby standard library. With one exception `ScheduledTask`
48
+ # behaves identically to [Future](Observable) with regard to these modules.
49
+ #
50
+ # @example Basic usage
51
+ #
52
+ # require 'concurrent'
53
+ # require 'thread' # for Queue
54
+ # require 'open-uri' # for open(uri)
55
+ #
56
+ # class Ticker
57
+ # def get_year_end_closing(symbol, year)
58
+ # uri = "http://ichart.finance.yahoo.com/table.csv?s=#{symbol}&a=11&b=01&c=#{year}&d=11&e=31&f=#{year}&g=m"
59
+ # data = open(uri) {|f| f.collect{|line| line.strip } }
60
+ # data[1].split(',')[4].to_f
61
+ # end
62
+ # end
63
+ #
64
+ # # Future
65
+ # price = Concurrent::Future.execute{ Ticker.new.get_year_end_closing('TWTR', 2013) }
66
+ # price.state #=> :pending
67
+ # sleep(1) # do other stuff
68
+ # price.value #=> 63.65
69
+ # price.state #=> :fulfilled
70
+ #
71
+ # # ScheduledTask
72
+ # task = Concurrent::ScheduledTask.execute(2){ Ticker.new.get_year_end_closing('INTC', 2013) }
73
+ # task.state #=> :pending
74
+ # sleep(3) # do other stuff
75
+ # task.value #=> 25.96
76
+ #
77
+ # @example Successful task execution
78
+ #
79
+ # task = Concurrent::ScheduledTask.new(2){ 'What does the fox say?' }
80
+ # task.state #=> :unscheduled
81
+ # task.execute
82
+ # task.state #=> pending
83
+ #
84
+ # # wait for it...
85
+ # sleep(3)
86
+ #
87
+ # task.unscheduled? #=> false
88
+ # task.pending? #=> false
89
+ # task.fulfilled? #=> true
90
+ # task.rejected? #=> false
91
+ # task.value #=> 'What does the fox say?'
92
+ #
93
+ # @example One line creation and execution
94
+ #
95
+ # task = Concurrent::ScheduledTask.new(2){ 'What does the fox say?' }.execute
96
+ # task.state #=> pending
97
+ #
98
+ # @example Failed task execution
99
+ #
100
+ # task = Concurrent::ScheduledTask.execute(2){ raise StandardError.new('Call me maybe?') }
101
+ # task.pending? #=> true
102
+ #
103
+ # # wait for it...
104
+ # sleep(3)
105
+ #
106
+ # task.unscheduled? #=> false
107
+ # task.pending? #=> false
108
+ # task.fulfilled? #=> false
109
+ # task.rejected? #=> true
110
+ # task.value #=> nil
111
+ # task.reason #=> #<StandardError: Call me maybe?>
112
+ #
113
+ # @example Task execution with observation
114
+ #
115
+ # observer = Class.new{
116
+ # def update(time, value, reason)
117
+ # puts "The task completed at #{time} with value '#{value}'"
118
+ # end
119
+ # }.new
120
+ #
121
+ # task = Concurrent::ScheduledTask.new(2){ 'What does the fox say?' }
122
+ # task.add_observer(observer)
123
+ # task.execute
124
+ # task.pending? #=> true
125
+ #
126
+ # # wait for it...
127
+ # sleep(3)
128
+ #
129
+ # #>> The task completed at 2013-11-07 12:26:09 -0500 with value 'What does the fox say?'
130
+ #
131
+ # @!macro monotonic_clock_warning
132
+ #
133
+ # @!macro [attach] deprecated_scheduling_by_clock_time
134
+ #
135
+ # @note Scheduling is now based on a monotonic clock. This makes the timer much
136
+ # more accurate, but only when scheduling by passing a delay in seconds.
137
+ # Scheduling a task based on a clock time is deprecated. It will still work
138
+ # but will not be supported in the 1.0 release.
8
139
class ScheduledTask < IVar
9
140
10
- attr_reader :schedule_time
141
+ attr_reader :delay
11
142
12
- def initialize ( intended_time , opts = { } , &block )
143
+ # @!macro deprecated_scheduling_by_clock_time
144
+ def initialize ( delay , opts = { } , &block )
13
145
raise ArgumentError . new ( 'no block given' ) unless block_given?
14
- TimerSet . calculate_schedule_time ( intended_time ) # raises exceptons
146
+ @delay = TimerSet . calculate_interval ( delay )
15
147
16
148
super ( NO_VALUE , opts )
17
149
18
150
self . observers = CopyOnNotifyObserverSet . new
19
- @intended_time = intended_time
20
151
@state = :unscheduled
21
152
@task = block
22
153
@executor = OptionsParser ::get_executor_from ( opts ) || Concurrent . configuration . global_operation_pool
23
154
end
24
155
25
- # @since 0.5.0
26
156
def execute
27
157
if compare_and_set_state ( :pending , :unscheduled )
28
- now = Time . now
29
- @schedule_time = TimerSet . calculate_schedule_time ( @intended_time , now )
30
- Concurrent ::timer ( @schedule_time . to_f - now . to_f ) { @executor . post ( &method ( :process_task ) ) }
158
+ @schedule_time = Time . now + @delay
159
+ Concurrent ::timer ( @delay ) { @executor . post ( &method ( :process_task ) ) }
31
160
self
32
161
end
33
162
end
34
163
35
- # @since 0.5.0
36
- def self . execute ( intended_time , opts = { } , &block )
37
- return ScheduledTask . new ( intended_time , opts , &block ) . execute
164
+ # @!macro deprecated_scheduling_by_clock_time
165
+ def self . execute ( delay , opts = { } , &block )
166
+ return ScheduledTask . new ( delay , opts , &block ) . execute
167
+ end
168
+
169
+ # @deprecated
170
+ def schedule_time
171
+ warn '[DEPRECATED] time is now based on a monotonic clock'
172
+ @schedule_time
38
173
end
39
174
40
175
def cancelled?
@@ -54,12 +189,6 @@ def cancel
54
189
end
55
190
alias_method :stop , :cancel
56
191
57
- def add_observer ( *args , &block )
58
- if_state ( :unscheduled , :pending , :in_progress ) do
59
- observers . add_observer ( *args , &block )
60
- end
61
- end
62
-
63
192
protected :set , :fail , :complete
64
193
65
194
private
0 commit comments