You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Haskell实现可随时退出、多次运行的计时器问题求助

优化思路
  • 解决SIGINT响应延迟问题:原实现中threadDelay是阻塞操作,必须等满1秒才会检查MVar状态。你可以把整秒等待拆分为多个短时间片轮询MVar,或是使用并发工具同时执行等待逻辑和MVar监听,一旦MVar有写入就立刻中断等待,响应延迟可控制在毫秒级。
  • 解决连续SIGINT触发程序终止问题:原代码使用CatchOnce注册信号处理函数,第一次触发SIGINT后处理器就会被注销,第二次触发就会走系统默认的终止逻辑。把CatchOnce替换为Catch即可永久绑定SIGINT处理逻辑,多次触发只会往当前绑定的MVar写入值,不会终止程序。
无额外依赖实现方案

仅依赖base和unix包,无需引入第三方库:

import Control.Concurrent (MVar, newEmptyMVar, tryTakeMVar, putMVar, threadDelay)
import System.Posix.Signals (installHandler, sigINT, Handler(..))

-- 可中断的延迟函数:最多等待delayUs微秒,提前收到MVar信号就返回True
interruptibleDelay :: MVar () -> Int -> IO Bool
interruptibleDelay v delayUs = go delayUs
  where
    -- 每次轮询间隔10毫秒,可根据需要调整响应灵敏度
    sliceUs = 10000
    go remaining = if remaining <= 0
      then return False
      else do
        threadDelay (min remaining sliceUs)
        tryTakeMVar v >>= \case
          Just _ -> return True
          Nothing -> go (remaining - sliceUs)

timer1 :: MVar () -> Int -> IO Int
timer1 v sec = do
  putStrLn (show sec)
  interrupted <- interruptibleDelay v 1000000
  if interrupted
    then return sec
    else if sec > 0 then timer1 v (sec - 1) else return 0

main :: IO ()
main = do
  -- 第一轮计时
  v1 <- newEmptyMVar
  installHandler sigINT (Catch $ putMVar v1 ()) Nothing
  res1 <- timer1 v1 5
  putStrLn $ "timer1: " ++ show res1

  -- 第二轮计时
  v2 <- newEmptyMVar
  -- 更新信号处理器绑定到新的MVar
  installHandler sigINT (Catch $ putMVar v2 ()) Nothing
  res2 <- timer1 v2 5
  putStrLn $ "timer1: " ++ show res2
基于async库的简洁实现

如果允许引入async依赖,不需要手动实现轮询逻辑,代码更简洁:

import Control.Concurrent (MVar, newEmptyMVar, takeMVar, putMVar, threadDelay)
import Control.Concurrent.Async (race)
import System.Posix.Signals (installHandler, sigINT, Handler(..))

timer1 :: MVar () -> Int -> IO Int
timer1 v sec = do
  putStrLn (show sec)
  -- race会同时执行两个IO,任意一个完成就自动取消另一个
  race (threadDelay 1000000) (takeMVar v) >>= \case
    -- takeMVar先完成,说明触发了SIGINT
    Right _ -> return sec
    -- 延迟先完成,继续计时
    Left _ -> if sec > 0 then timer1 v (sec - 1) else return 0

main :: IO ()
main = do
  v1 <- newEmptyMVar
  installHandler sigINT (Catch $ putMVar v1 ()) Nothing
  res1 <- timer1 v1 5
  putStrLn $ "timer1: " ++ show res1

  v2 <- newEmptyMVar
  installHandler sigINT (Catch $ putMVar v2 ()) Nothing
  res2 <- timer1 v2 5
  putStrLn $ "timer1: " ++ show res2

内容的提问来源于stack exchange,提问作者Theerawat Kiatdarakun

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 03:00:02