5

对于这个答案,我编写了如下代码:

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"

目标是确保最终输出是"AB"-"\b"如果线程尚未打印最终结果,则打印它。在存在的 Ruby 中,该代码对我来说似乎很乱begin/rescue/ensure。所以我尝试了一些其他的实现show_wait_spinner;他们都无法确保"AB"始终是输出,并且永远不会"A*B"or "AB*"

有没有更干净、更 Ruby 风格的方式来实现这个逻辑?

通过 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

......但我的逻辑必须是关闭的,因为这总是导致“A * B”。

通过线程局部变量在循环结束时停止

第二次尝试导致有时打印“A*B”,有时打印“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

杀死并确保线程

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

提高和拯救线程

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
4

3 回答 3

6

与其杀死你的线程,不如翻转一个变量,让它在预定义的点停止?如果您让它循环并在循环结束时退出,您将不会遇到太多麻烦。

例如:

def show_wait_spinner
  running = true

  spinner = Thread.new do
    while (running) do
      print "*"
      sleep 0.1
      print "\b"  
    end
  end

  yield
  running = false
  spinner.join
end

print "A"
show_wait_spinner{ sleep rand }
puts "B"

当您打电话时,Thread#kill您不知道线程在哪里,并且线程没有机会清理它正在做的事情。如果您礼貌的“停止运行”请求不被尊重,您可以随时终止线程。

于 2012-04-21T23:23:12.630 回答
3

我更喜欢您的同步停止条件方法,但您有几个错误:

  1. 设置停止变量后,您几乎立即结束程序,因此停止线程的是程序退出,而不是循环中的条件测试;使用 Thread#join 等待线程正常退出,您将获得所需的一致输出。
  2. break在同步块中打破块,而不是循环。

    def show_wait_spinner
      stop = false
      stopm = Mutex.new
      spinner = Thread.new{
        loop{
          print "*"
          sleep 0.1
          print "\b"
          break if stopm.synchronize{ stop }
        }
      }
      yield
      stopm.synchronize{ stop = true }
      spinner.join
      STDOUT.flush
    end
    
    print "A"
    show_wait_spinner{ sleep rand }
    puts "B"
    

我会避免任何涉及 Thread#raise 和 Thread#kill 的解决方案,因为它们的行为永远无法预测和正确,请参阅Charles Nutter 关于这些方法的破坏性的咆哮

Mutex#synchronize 仅对于这种简单的布尔翻转是必需的,如果您真的非常关心父线程设置 var 时竞争条件周围的精确时间,在本示例中这不太可能,因此您可以避免开销和只需正常设置和读取stop

于 2012-04-21T23:36:22.577 回答
2

在您的互斥锁示例中,您需要等待线程完成才能退出该方法。当前您设置stop为 true,然后退出该方法,打印B并在微调器线程能够唤醒并打印最后一个退格键以删除*字符之前结束您的脚本。

此外,breakinstopm.synchronize{ break if stop }仅退出内部块,而不是循环,因此您需要使用 catch/throw 或其他东西。

但是,您不需要互斥锁。这在 1.9.3 中对我有用:

def show_wait_spinner
  exit = false
  spinner = Thread.new{
    loop{
      print "*"
      sleep 0.1
      print "\b" 
      break if exit
    }    
  }
  yield
  exit = true
  spinner.join
end

在顶部添加$stdout.sync = true使其在 1.8.7 中工作。

于 2012-04-21T23:27:52.300 回答