Ruby Async Gem: What is a basic usage example?
Asked Answered
E

1

7

With Ruby 3.0 the async gem is now compatible with blocking IO in standard library functions and I wanted to understand the basic functionality but am already confused by a simple example:

require 'async'

n = 10
n.times do |i|
  Async do
    HTTParty.get("https://httpbin.org/delay/1.6")
  end
end

This doesn't show any parallelism. Looking in the Gem's documentation about Kernel#async says:

Run the given block of code in a task, asynchronously, creating a reactor if necessary.

But the project documentation seems to clear it up:

When invoked at the top level, will create and run a reactor, and invoke the block as an asynchronous task. Will block until the reactor finishes running.

So to make the example from above work:

require 'async'

n = 10
Async do
  n.times do |i|
    Async do
      HTTParty.get("https://httpbin.org/delay/1.6")
    end
  end
end

This works, but seems confusing to the reader. How would we know as readers that the first Async do is blocking while the others are not?

Thus the question: What is the canonical basic usage of the async gem?

Further reading:

Engorge answered 1/11, 2021 at 8:28 Comment(5)
When invoked at the top level, will create and run a reactor, and invoke the block as an asynchronous task. Will block until the reactor finishes running - only top level Async will be blockedUnemployable
From same docs: When invoked within an existing reactor task, it will run the given block asynchronouslyUnemployable
Hey Fabio, makes sense, but how I know when looking at a piece of code if I am top level or not?Engorge
Top-level is top level of the .rb file (try puts to_s). Anything in the module or class block is not top level.Unemployable
No, this isn't what the documentation means. In this case it is about runtime nesting of Ractors.Engorge
P
0

If your question is "what is a basic example of the async gem", then your second code snippet is one.

If your question is "why is the API like this?", that's something you'd likely have to ask the developer(s) working on this gem on its issue board.

If your question is "how can you tell if running an Async::Reactor (which is what Kernel#Async does) will be blocking, you can call Async#Task.current?:

require 'async'
require 'net/http'

def log_whether_blocking
  if Async::Task.current?
    puts "Async::Task#current? is truthy, calling `Kernel#Async` will not block"
  else
    puts "Async::Task#current? is falsey, calling `Kernel#Async` will block"
  end
end

n = 10
Async do
  n.times do |i|
    log_whether_blocking
    Async do
      Net::HTTP.get(URI("https://httpbin.org/delay/1.6"))
    end
  end
end

n.times do |i|
  log_whether_blocking
  Async do
    Net::HTTP.get(URI("https://httpbin.org/delay/1.6"))
  end
end

This gives the output

Async::Task#current? is truthy, calling `Kernel#Async` will not block
Async::Task#current? is truthy, calling `Kernel#Async` will not block
Async::Task#current? is truthy, calling `Kernel#Async` will not block
Async::Task#current? is truthy, calling `Kernel#Async` will not block
Async::Task#current? is truthy, calling `Kernel#Async` will not block
Async::Task#current? is truthy, calling `Kernel#Async` will not block
Async::Task#current? is truthy, calling `Kernel#Async` will not block
Async::Task#current? is truthy, calling `Kernel#Async` will not block
Async::Task#current? is truthy, calling `Kernel#Async` will not block
Async::Task#current? is truthy, calling `Kernel#Async` will not block
Async::Task#current? is falsey, calling `Kernel#Async` will block
Async::Task#current? is falsey, calling `Kernel#Async` will block
Async::Task#current? is falsey, calling `Kernel#Async` will block
Async::Task#current? is falsey, calling `Kernel#Async` will block
Async::Task#current? is falsey, calling `Kernel#Async` will block
Async::Task#current? is falsey, calling `Kernel#Async` will block
Async::Task#current? is falsey, calling `Kernel#Async` will block
Async::Task#current? is falsey, calling `Kernel#Async` will block
Async::Task#current? is falsey, calling `Kernel#Async` will block
Async::Task#current? is falsey, calling `Kernel#Async` will block

While it may be a potential footgun for those unfamiliar with the API, you can always ensure running a set of Async::Reactors will be done concurrently if you wrap it in a Kernel#Async call, though in some cases it may be an unnecessary to do so.

Pericles answered 1/3 at 6:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.