14

Control-C (UserInterrupt) をキャッチする非常に単純な read-eval-print-loop を Haskell で作成しました。ただし、このプログラムをコンパイルして実行すると、常に最初の Control-C がキャッチされ、2 番目の Control-C で終了コード 130 で常に中止されます。 Control-Cs は、常にこのように行われます。私は単純なものが欠けているに違いないことを知っています...助けてください、ありがとう!

注: これはベース 4 の例外であるため、Control.Exception であり、Control.OldException ではありません。

import Control.Exception as E
import System.IO

main :: IO ()
main = do hSetBuffering stdout NoBuffering
          hSetBuffering stdin NoBuffering
          repLoop

repLoop :: IO ()
repLoop
  = do putStr "> "
       line <- interruptible "<interrupted>" getLine
       if line == "exit"
          then putStrLn "goodbye"
          else do putStrLn $ "input was: " ++ line
                  repLoop

interruptible :: a -> IO a -> IO a
interruptible a m
  = E.handleJust f return m
  where
    f UserInterrupt
      = Just a
    f _
      = Nothing
4

3 に答える 3

9

Wei Hu は正しいです。Haskell ランタイム システムは、2 回目の control-C が押されると、意図的にプログラムを中止します。期待される動作を得るには:

import Control.Exception as E
import Control.Concurrent
import System.Posix.Signals

main = do
  tid <- myThreadId
  installHandler keyboardSignal (Catch (throwTo tid UserInterrupt)) Nothing
  ... -- rest of program
于 2011-10-29T19:31:17.673 に答える
5

免責事項: 私は GHC の内部構造に精通していません。私の答えは、ソース コードの grep、コメントの読み取り、および推測に基づいています。

定義したmain関数は、実際にはrunMainIOdefined in でラップされていますGHC.TopHandler(これは、TcRnDriver.lhs を見ることでさらに確認できます)。

-- | 'runMainIO' is wrapped around 'Main.main' (or whatever main is
-- called in the program).  It catches otherwise uncaught exceptions,
-- and also flushes stdout\/stderr before exiting.
runMainIO :: IO a -> IO a
runMainIO main = 
    do 
      main_thread_id <- myThreadId
      weak_tid <- mkWeakThreadId main_thread_id
      install_interrupt_handler $ do
           m <- deRefWeak weak_tid 
           case m of
               Nothing  -> return ()
               Just tid -> throwTo tid (toException UserInterrupt)
      a <- main
      cleanUp
      return a
    `catch`
      topHandler

そしてinstall_interrupt_handler、次のように定義されています。

install_interrupt_handler :: IO () -> IO ()
#ifdef mingw32_HOST_OS
install_interrupt_handler handler = do
  _ <- GHC.ConsoleHandler.installHandler $
     Catch $ \event -> 
        case event of
           ControlC -> handler
           Break    -> handler
           Close    -> handler
           _ -> return ()
  return ()
#else
#include "rts/Signals.h"
-- specialised version of System.Posix.Signals.installHandler, which
-- isn't available here.
install_interrupt_handler handler = do
   let sig = CONST_SIGINT :: CInt
   _ <- setHandler sig (Just (const handler, toDyn handler))
   _ <- stg_sig_install sig STG_SIG_RST nullPtr
     -- STG_SIG_RST: the second ^C kills us for real, just in case the
     -- RTS or program is unresponsive.
   return ()

Linux では、stg_sig_installを呼び出す C 関数ですsigaction。パラメータSTG_SIG_RSTは に変換されSA_RESETHANDます。Windows では、処理が異なるため、おそらく ja の観察が説明されます。

于 2010-02-28T22:21:37.860 に答える
3

私にとって (少なくとも Linux では) 最も信頼できる解決策は、System.Posix.Signals を使用してシグナル ハンドラーをインストールすることです。私はこれを必要としない解決策を望んでいましたが、質問を投稿した本当の理由は、GHC がそのように振る舞った理由を知りたかったからです。#haskell で説明されているように、GHC がこのように動作するため、アプリケーションがハングした場合にユーザーがいつでも Control-C を実行できるという説明が考えられます。それでも、GHC が、私たちが頼ったやや低レベルの方法なしで、この動作に影響を与える方法を提供してくれれば素晴らしいことです :)。

于 2010-03-01T11:28:29.020 に答える