WxHaskell を使用して、TCP (Data.Binary を使用してデコード) を使用して状態の更新を通知するプログラムの状態をグラフィカルに表示しています。アップデートを受信したら、表示を更新したい。そのため、GUI の表示を非同期で更新する必要があります。コマンドラインプロセスを非同期で実行することは知ってprocessExecAsync
いますが、これは私が望んでいるものではないと思います。
3 に答える
これは、トランザクション変数(つまり、ソフトウェアトランザクショナルメモリ)を使用した大まかなコードです。IORef、MVar、または他の多数の構成を使用できます。
main = do
recvFunc <- initNetwork
cntTV <- newTVarIO 0
forkIO $ threadA recvFunc cntTV
runGUI cntTV 0
上記でプログラムを開始し、ネットワークと共有変数を初期化しますcntTV
threadA recvCntFromNetwork cntTVar = forever $ do
cnt <- recvCntFromNetwork
atomically (writeTVar cntTVar cnt)
threadA
ネットワークからデータを受信し、カウンターの新しい値を共有変数に書き込みます。
runGUI cntTVar currentCnt = do
counter <- initGUI
cnt <- atomically $ do
cnt <- readTVar cntTVar
if (cnt == currentCnt)
then retry
else return cnt
updateGUICounter counter cnt
runGUI cntTVar cnt
runGUI
共有変数を読み取り、変更がある場合はGUIカウンターを更新します。参考までに、runGUIスレッドは変更されるretry
までウェイクアップしcntTVar
ないため、これはCPUを占有するポーリングループではありません。
このコードでは、、、、およびという名前updateGUICounter
の関数があると想定しています。Hoogleを使用して、まだ知らない他の関数の場所を見つけ、各モジュールについて少し学ぶことをお勧めします。initGUI
initNetwork
私はうまくいくように見える一種のハックを思いついた。つまり、イベントタイマーを使用して、更新キューを確認します。
startClient :: IO (TVar [Update])
startClient = /*Connect to server,
listen for updates and add to queue*/
gui :: TVar [Update] -> IO ()
gui trdl = do
f <- frame [text := "counter", visible := False]
p <- panel f []
st <- staticText p []
t <- timer f [interval := 10, on command := updateGui st]
set f [layout := container p $ fill $ widget st, clientSize := (sz 200 100), visible := True]
where
updateGui st = do
rdl <- atomically $ readTVar trdl
atomically $ writeTVar trdl []
case rdl of
[] -> return ()
dat : dl -> set st [text := (show dat)]
main :: IO ()
main = startClient >>= start gui
したがって、クライアントはTCP接続で更新をリッスンし、それらをキューに追加します。10ミリ秒ごとに、このキューをチェックし、静的テキストウィジェットに最新の更新を表示するアクションを持つイベントが発生します。
より良い解決策があれば、私に知らせてください!
http://snipplr.com/view/17538/でビジーウェイトなしで解決策を見つけました
ただし、既存のIDとの競合を回避するために、より高いeventIdを選択する場合があります。
これが私のモジュールhttp://code.haskell.org/alsa/gui/src/Common.hsからのいくつかのコードです:
myEventId :: Int
myEventId = WXCore.wxID_HIGHEST+100
-- the custom event ID, avoid clash with Graphics.UI.WXCore.Types.varTopId
-- | the custom event is registered as a menu event
createMyEvent :: IO (WXCore.CommandEvent ())
createMyEvent =
WXCore.commandEventCreate WXCore.wxEVT_COMMAND_MENU_SELECTED myEventId
registerMyEvent :: WXCore.EvtHandler a -> IO () -> IO ()
registerMyEvent win io =
WXCore.evtHandlerOnMenuCommand win myEventId io
reactOnEvent, reactOnEventTimer ::
SndSeq.AllowInput mode =>
Int -> WX.Window a -> Sequencer mode ->
(Event.T -> IO ()) ->
IO ()
reactOnEvent _interval frame (Sequencer h _) action = do
mvar <- MVar.newEmptyMVar
void $ forkIO $ forever $ do
MVar.putMVar mvar =<< Event.input h
WXCore.evtHandlerAddPendingEvent frame =<< createMyEvent
registerMyEvent frame $
MVar.takeMVar mvar >>= action
-- naive implementation using a timer, requires Non-Blocking sequencer mode
reactOnEventTimer interval frame sequ action =
void $
WX.timer frame [
WX.interval := interval,
on command := getWaitingEvents sequ >>= mapM_ action]
このコードは、問題を処理する2つの方法を示しています。
reactOnEventTimer
WXタイマーを使用してビジーウェイトを実行します。reactOnEvent
イベントが実際に到着したときにのみアクティブになります。これが推奨されるソリューションです。
私の例では、ALSAMIDIシーケンサーメッセージを待ちます。Event.input呼び出しは、次のALSAメッセージが来るのを待ちます。はaction
、Event.inputの結果、つまり着信ALSAメッセージを取得しますが、WXスレッドで実行されます。