Skip to content

Instantly share code, notes, and snippets.

@mattwelke
Created November 22, 2016 21:06
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattwelke/b92205e44a244e1d59aa7aafef52480a to your computer and use it in GitHub Desktop.
Save mattwelke/b92205e44a244e1d59aa7aafef52480a to your computer and use it in GitHub Desktop.
Async Controller Actions in Ruby on Rails
# 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