For this answer I wrote code like:
def show_wait_spinner
dirty = false
spinner = Thread.new{
loop{
print "*"
dirty = true
sleep 0.1
print "\b"
dirty = false
}
}
yield
spinner.kill
print "\b" if dirty
end
print "A"
show_wait_spinner{ sleep rand }
puts "B"
The goal is to ensure that the final output was "AB"—to print a final "\b" if it was not already printed by the thread. That code seems messy to me in Ruby where begin/rescue/ensure exists. So I tried some other implementations of show_wait_spinner; all of them fail to ensure that "AB" is always the output, and never "A*B" or "AB*".
Is there a cleaner, more Ruby-esque way to implement this logic?
Stop at end of loop via Mutex
def show_wait_spinner
stop = false
stopm = Mutex.new
spinner = Thread.new{
loop{
print "*"
sleep 0.1
print "\b"
stopm.synchronize{ break if stop }
}
}
yield
stopm.synchronize{ stop = true }
STDOUT.flush
end
…but my logic must be off, since this always results in “A*B”.
Stop at end of loop via Thread-local variable
This second attempt results in sometimes “A*B” being printed, sometimes “AB”:
def show_wait_spinner
stop = false
spinner = Thread.new{
Thread.current[:stop] = false
loop{
print "*"
sleep 0.1
print "\b"
stopm.synchronize{ break if Thread.current[:stop] }
}
}
yield
spinner[:stop] = true
STDOUT.flush
end
Kill and Ensure the Thread
def show_wait_spinner
spinner = Thread.new{
dirty = false
begin
loop{
print "*"
dirty = true
sleep 0.1
print "\b"
dirty = false
}
ensure
print "\b" if dirty
end
}
yield
spinner.kill
STDOUT.flush
end
Raise and Rescue the Thread
def show_wait_spinner
spinner = Thread.new{
dirty = false
begin
loop{
print "*"
dirty = true
sleep 0.1
print "\b"
dirty = false
}
rescue
puts "YAY"
print "\b" if dirty
end
}
yield
spinner.raise
STDOUT.flush
end
In your mutex example, you need to wait for the Thread to finish before exiting the method. Currently you set
stopto true, then exit the method, printBand end your script before the spinner thread is able to wake up and print the last backspace to delete the*character.Also, the
breakinstopm.synchronize{ break if stop }only exits the inner block, not the loop, so you need to use catch/throw or something.However, you don’t need the mutex. This works for me in 1.9.3:
Adding
$stdout.sync = trueat the top makes it work in 1.8.7.