Reputation: 7230
I want to do a function which call a remote service each second. To do this, I have something like this :
stop = false
text = ""
while stop == false
r = RestClient.post 'http://example.com'
text += r.to_str
sleep 1
# after a treatment, the value of stop will set to true
end
The problem is than the program is blocked until the http request is done and I don't want it. I can put this code in a subprocess but I want to keep the result in call order. For example, I can have this request :
time | anwser
--------------
10 | Happy
100 | New
10 | Year
The second request is longer than the third so, with threads, I will have the third result before the second and the value of the variable text
will be HappyYearNew
and I want HappyNewYear
.
I there a way to have multiple process and to keep the original order? It's a very little program, I don't want to have to install a server like redis if it's possible.
Upvotes: 1
Views: 865
Reputation: 7937
Since ruby-1.9, hash keys order is guaranteed. A simple solution here would be to take advantage of that, by putting your requests in a hash and storing their result accessing the hash element by its key :
requests = {
foo: [ 'a', 1 ],
bar: [ 'b', 5 ],
foobar: [ 'c', 2 ]
}
requests.each do |name, config|
Thread.new( name, config ) do |name, config|
sleep config[1]
requests[ name ] = config[0]
end
end
sleep 6
requests.each do |name, result|
puts "#{name} : #{result}"
end
Produces :
foo : a
bar : b
foobar : c
Thus, to match your provided code :
stop, i, text, requests = false, 0, '', {}
until stop
i += 1
requests[ i ] = nil
Thread.new( i ) do |key|
r = RestClient.post 'http://example.com'
requests[ i ] = r.to_str
sleep 1
# after a treatment, the value of stop will set to true
end
end
# you will have to join threads, here
text = requests.values.join
If the last example is good for you, you could even simplify that using an array. Array order is of course guaranteed too, and you can take advantage of ruby array dynamic size nature :
a = []
a[5] = 1
p a
=> [nil, nil, nil, nil, nil, 1]
So, previous example can be rewritten :
stop, i, text, requests = false, 0, '', []
until stop
i += 1
Thread.new( i ) do |key|
r = RestClient.post 'http://example.com'
requests[ i ] = r.to_str
sleep 1
# after a treatment, the value of stop will set to true
end
end
# you will have to join threads, here
text = requests.join
Upvotes: 1
Reputation: 35788
Here's a pretty simple solution with Threads. I have results and rmutex as instance variables, you could make them global, class, or a lot of other things:
stop = false
Thread.current[:results] = {}
Thread.current[:rmutex] = Mutex.new
counter = 0
while(!stop)
Thread.new(counter, Thread.current) do |idex, parent|
r = RestClient.post 'http://example.com'
parent[:rmutex].lock
parent[:results][idex] = r.to_str
parent[:rmutex].unlock
end
sleep 1
end
text = Thread.current[:results].to_a.sort_by {|o| o[0]}.map {|o| o[1]}.join
This works by storing the "index" into fetching that each threads operation is at, storing the result with its index, and putting it all together after sorting by index at the end.
Upvotes: 0