Search code examples
rubyconcurrencyconcurrent-ruby

How can I call a block within the execute method of a ScheduledTask?


I'm trying to call a block within the Concurrent::ScheduledTask#execute method, but the block itself never is executed.

I also tried using Concurrent::ScheduledTask#new method, but the results are the same. I feel there may be a fundamental issue here that I'm missing. Any help would be much appreciated!

require 'concurrent'
##
# A basic Event
class Event
  attr_accessor :ticks
  # @param ticks [Numeric] The amount of ticks we wait before executing this event
  def initialize(ticks = 1.0)
    @ticks = ticks
    puts "Event created with #{@ticks} ticks"
  end
  # Calls the block of this event for execution.
  def execute(&block)
    if !block_given?
      raise AbstractEventExecution.new(self)
    else
      Concurrent::ScheduledTask.execute(@ticks *= 0.75) { block.call }
      puts "Executed in #{@ticks} ticks"
    end
  end
end

class AbstractEventExecution < StandardError
  attr_accessor :event
  def initialize(event)
    @event = event
    super("The Event #{event} was not provided an execution block and is abstract.")
  end
end

event1 = Event.new(105)
event2 = Event.new(1000)
event3 = Event.new(50)
event1.execute { puts "hai from event 1" }
event2.execute { puts "hai from event 2" }
event3.execute { puts "hai from event 3" }

the output is as follows:

Event created with 105 ticks
Event created with 1000 ticks
Event created with 50 ticks
executing an event...
Executed in 78.75 ticks
executing an event...
Executed in 750.0 ticks
executing an event...
Executed in 37.5 ticks

I'm not sure why puts "hai from event x" is never shown at all. Additionally, there is no delay when this executes. There should be a 78.75, 750.0, and 37.5 second delay respectively and there's none at all!


Solution

  • Both Concurrent::ScheduledTask#new and Concurrent::ScheduledTask#execute returns a [ScheduledTask] object, then the main thread exits.

    That's why "hai from event x" is never shown.

    require 'concurrent'
    ##
    # A basic Event
    class Event
      attr_accessor :ticks
      # @param ticks [Numeric] The amount of ticks we wait before executing this event
      def initialize(ticks = 1.0)
        @ticks = ticks
        puts "Event created with #{@ticks} ticks"
      end
    
      # Calls the block of this event for execution.
      def execute(&block)
        if !block_given?
          raise AbstractEventExecution.new(self)
        else
          task = Concurrent::ScheduledTask.execute(@ticks *= 0.75) { block.call }
          puts "Executed in #{@ticks} ticks"
          task
        end
      end
    end
    
    class AbstractEventExecution < StandardError
      attr_accessor :event
    
      def initialize(event)
        @event = event
        super("The Event #{event} was not provided an execution block and is abstract.")
      end
    end
    
    event1 = Event.new(1)
    event2 = Event.new(2)
    event3 = Event.new(3)
    t1 = event1.execute { puts "hai from event 1" }
    t2 = event2.execute { puts "hai from event 2" }
    t3 = event3.execute { puts "hai from event 3" }
    
    # waiting for all threads to finish
    [t1, t2, t3].map(&:wait)