Created
November 22, 2016 21:06
-
-
Save mattwelke/b92205e44a244e1d59aa7aafef52480a to your computer and use it in GitHub Desktop.
Async Controller Actions in Ruby on Rails
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Full repo: | |
# https://gitlab.com/mwelke/rails-async-example | |
Future = Concurrent::Future | |
class OperationController < ApplicationController | |
def sync | |
ops = [1, 2, 3] | |
ops.each do |n| | |
sleep n # Could also be accessing REST APIs etc | |
end | |
# At this point, all of the synchronous operations above were completed, | |
# one after another. | |
render text: "Finished #{ops.length} blocking operations synchronously." | |
end | |
def async | |
# An example using raw threads, demonstrating the need to use a Mutex to | |
# make it thread safe if applicable. | |
ops = [1, 2, 3] | |
threads = [] | |
mutex = Mutex.new | |
ops.each do |n| | |
threads << Thread.new do | |
# Wrapping code inside a Mutex makes it atomic and therefore | |
# thread safe. This is important if each thread modifies data, like | |
# appending API responses to an array. In this example, "sleep" is | |
# already thread safe. When code is executed synchronously, this isn't | |
# a concern. | |
mutex.synchronize do | |
sleep n # Could also be accessing REST APIs etc | |
end | |
end | |
end | |
# All threads started will join up here. The execution won't continue past | |
# this point until they're all finished. | |
threads.each { |t| t.join } | |
# At this point, could continue the controller action knowing you've got | |
# the data you need from the multiple async operations began above. | |
render text: "Finished #{ops.length} blocking operations asynchronously." | |
end | |
def futures | |
# This begins executing after the "end" and "sum" is a Concurrent::Future | |
# object. When "value" is invoked on sum, it will cause the main thread | |
# to block until sum has finished its asynchronous work on another | |
# thread (if that work hasn't yet finished) and then return sum's value as | |
# a result of that work. We invoke value below. | |
sum = Future.execute do | |
sleep 3 | |
2 + 2 | |
end | |
# We do the same for product, except that this is the much longer-running | |
# asynchronous task. | |
product = Future.execute do | |
sleep 7 | |
1.5 * 3 | |
end | |
# On this line, both Futures will block until their work is done. This will | |
# take 7 seconds in this example, because both Futures are running on their | |
# own threads concurrently, and we only need 7 seconds (not 10) for both to | |
# complete. | |
larger = [sum.value, product.value].max | |
# 7 seconds after the request is received, a response is sent. | |
render text: "The larger of the sum of 2 and 2 and the product of 1.5 and 3 is #{larger}." | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment