vwait — 변수가 쓰일 때까지 이벤트 처리하기
vwait — 변수가 쓰일 때까지 이벤트 처리하기
어떤 변수에 값이 쓰일 때까지 기다리면서 그 동안 이벤트를 처리하고 싶을 때 vwait를 써요. 이벤트 루프에 들어가, 어떤 이벤트 핸들러가 그 변수에 값을 설정할 때까지 처리하다가 그 시점에 돌아오는 방식이에요. 이벤트 기반 프로그래밍에서 매우 흔한 패턴이라, 스크립트를 멈추지 않고 비동기 작업의 완료를 기다리는 데 요긴해요.
출처: 문서
본문
문법
vwait varName
이 명령어는 Tcl 이벤트 루프에 들어가 이벤트를 처리하는데, 준비된 이벤트가 없으면 애플리케이션을 블로킹해요. 어떤 이벤트 핸들러가 전역 변수 varName의 값을 설정할 때까지 이벤트 처리를 계속해요. varName이 설정되면, 그 변수를 수정한 이벤트 핸들러가 끝나는 즉시 vwait 명령어가 반환돼요. varName 인자는 항상 전역 네임스페이스 기준의 변수 이름으로 해석되지만, 완전 한정 이름을 주면 아무 네임스페이스의 변수도 가리킬 수 있어요.
어떤 경우에는 vwait 명령어가 varName이 설정된 직후에 바로 반환되지 않을 수 있어요. 그건 varName을 설정하는 이벤트 핸들러가 즉시 끝나지 않는 경우에 일어나요. 예를 들어 이벤트 핸들러가 varName을 설정한 뒤 그 안에서 다른 변수를 기다리기 위해 vwait를 또 호출하면, 오랫동안 반환되지 않을 수 있어요. 그 동안 최상위 vwait는 그 이벤트 핸들러가 끝나기를 기다리며 블로킹되므로, 그쪽도 반환할 수 없어요.(아래 'NESTED VWAITS BY EXAMPLE' 참조)
명확히 하면, 여러 vwait 호출은 중첩될지언정 병렬로 일어나지 않아요. 맨 바깥의 vwait 호출은 모든 안쪽 것들이 끝날 때까지 반환되지 않아요. 코드가 vwait 호출을 중첩하지 않도록(이벤트 콜백 안에 두지 않도록) 권장하고요, 그게 불가능한 경우에는 꼭 필요한 재진입 호출만 남도록 코드에 인터록(interlock) 변수를 추가하는 데 주의해야 해요. 일부 Tcl 패키지(예: http)의 동기 모드가 내부적으로 vwait를 사용한다는 점도 알아두세요. 이벤트 루프를 쓴다면 가능한 한 그 패키지들의 비동기 콜백 기반 모드를 쓰는 게 좋아요.
예제
어떤 이벤트가 exit를 호출할 때까지 이벤트 루프를 계속 돌리기. (다른 데서 쓰지 않는 아무 변수나 써도 되지만, forever라는 이름이 짧은 순간에도 의도를 떠올리게 해줘요.)
vwait forever
서버 소켓 연결을 5초 동안 기다리고, 그렇지 않으면 소켓을 닫고 스크립트를 계속 실행하기:
# 상태 초기화
after 5000 set state timeout
set server [socket -server accept 12345]
proc accept {args} {
global state connectionInfo
set state accepted
set connectionInfo $args
}
# 어떤 일이 일어날 때까지 기다리기
vwait state
# 발생했을 수 있는 이벤트 정리
close $server
after cancel set state timeout
# vwait이 끝난 방식에 따라 뭔가 하기...
switch $state {
timeout {
puts "no connection on port 12345"
}
accepted {
puts "connection: $connectionInfo"
puts [lindex $connectionInfo 0] "Hello there!"
}
}
네임스페이스 변수가 설정되기를 기다려 일정 시간 지연을 기다리는 명령어. 중첩 대기를 막기 위한 인터록을 포함해요.
namespace eval example {
variable v done
proc wait {delay} {
variable v
if {$v ne "waiting"} {
set v waiting
after $delay [namespace code {set v done}]
vwait [namespace which -variable v]
}
return $v
}
}
코루틴 안에서 실행할 때는 vwait를 쓰는 대신 바깥 이벤트 루프에 yield하고, 변수가 설정될 때(또는 그 후의 idle 순간에) 다시 시작되는 방법을 쓸 수 있어요.
coroutine task apply {{} {
# [after 1000] 시뮬레이션
after 1000 [info coroutine]
yield
# 평소처럼 전역 변수 설정을 예약
after 2000 {set var 1}
# [vwait var] 시뮬레이션
proc updatedVar {task args} {
after idle $task
trace remove variable ::var write "updatedVar $task"
}
trace add variable ::var write "updatedVar [info coroutine]"
yield
}}
중첩 vwait 예제
이 예제는 vwait 명령어가 중첩될 때 무슨 일이 생기는지 보여줘요. 예제 스크립트는 끝나지 않아요. a 변수 대기가 끝나지 않기 때문이죠. 그 vwait 명령어는 after로 예약된 스크립트가 끝나기를 여전히 기다리는데, 그 스크립트가 (바깥 vwait가 기다리던 a 설정이라는 이벤트가 이미 발생했는데도) 내부 vwait(b용)를 실행하고 있거든요.
after 500 {
puts "waiting for b"
vwait b
puts "b was set"
}
after 1000 {
puts "setting a"
set a 10
}
puts "waiting for a"
vwait a
puts "a was set"
puts "setting b"
set b 42
위 코드를 실행하면 다음 출력이 나와요:
waiting for a
waiting for b
setting a
vwait 명령어의 중첩 때문에 a was set은 b was set이 출력된 후에야 출력되고, 그런데 b는 바깥 vwait가 반환된 후에야 설정되므로 스크립트는 교착 상태(deadlock)에 빠져요. 이를 피하는 방법은 두 가지예요. 전체 프로그램을 continuation-passing 스타일로 구조화하거나, 코루틴을 써서 연속을 암묵적으로 만드는 거예요. 첫 번째 방법은 이렇게 써요:
after 500 {
puts "waiting for b"
trace add variable b write {apply {args {
global a b
trace remove variable ::b write
[lrange [info level 0] 0 1]
puts "b was set"
set ::done ok
}}}
}
after 1000 {
puts "setting a"
set a 10
}
puts "waiting for a"
trace add variable a write {apply {args {
global a b
trace remove variable a write [lrange [info level 0] 0 1]
puts "a was set"
puts "setting b"
set b 42
}}}
vwait done
두 번째 방법인 코루틴과 몇몇 도우미 프로시저로는 이렇게 해요:
# 코루틴 기반 대기-변수 명령어
proc waitvar globalVar {
trace add variable ::$globalVar write
[list apply {{v c args} {
trace remove variable $v write
[lrange [info level 0] 0 3]
after 0 $c
}} ::$globalVar [info coroutine]]
yield
}
# 코루틴 기반 대기-시간 명령어
proc waittime ms {
after $ms [info coroutine]
yield
}
coroutine task-1 eval {
puts "waiting for a"
waitvar a
puts "a was set"
puts "setting b"
set b 42
}
coroutine task-2 eval {
waittime 500
puts "waiting for b"
waitvar b
puts "b was set"
set done ok
}
coroutine task-3 eval {
waittime 1000
puts "setting a"
set a 10
}
vwait done
더 알아보기
global,update— 변수·이벤트 처리 관련 명령어- 관련 키워드:
asynchronous I/O,event,variable,wait