Использование upvar, когда процедуры вызываются событиями канала, и получение значения, на которое указывает указатель?

#tcl

Вопрос:

У меня есть два вопроса о следующем коде.

  1. Можно ли его использовать upvar , ReadLine когда он вызывается событием канала? Экспериментируя upvar , я использовал точно такой же сценарий, за исключением ReadLine того, что он был вызван напрямую, а не через событие; и это сработало нормально. Он терпит неудачу как есть, потому state($sock) что не распознается. Нужна ли другая #n ценность или она должна быть глобальной? Он попытался #2 , и это выдало ошибку, которая была плохим значением.
  2. В процедуре ReadLine я попытался сделать указатель $state($sock) таким, чтобы он указывал на сам список. В экспериментах, например , это хорошо работало lappend $sptr $op , но не сработало set row [lindex $sptr 0] , потому $sptr что есть state(1) . Тем не менее, это работает , если использовать set , например set row [lindex [set $sptr] 0] . Почему это происходит? Существует ли другой метод получения значения, на которое указывает указатель? Является ли попытка использовать указатели в Tcl плохой идеей? Означает ли тот факт, что тело процедур преобразуется в представление байт-кода, что использование указателей не имеет большого значения?

Спасибо.

 proc ClientConnect {sock client_ip client_port} {    
   if {![info exists state($sock)]} {set state($sock) {1}}; 
   chan configure $sock -buffering line -blocking 0 -translation crlf
   chan event $sock readable [list ReadLine $sock]
}

proc ReadLine {sock} {

  # The #1 in upvar is to first caller, which is ClientConnect.
  # Make a ptr to the array list rather than retrieving it each reference,
  # such that $sptr is state($sock)'s list.

  upvar #1 state r
  set sptr r($sock)
  puts stdout "x: [lindex $r($sock) 0]"; #Fails to recognize state.
  set row [lindex [set $sptr] 0]
}
 

Экспериментальный код, который, по-видимому, работает правильно:

 proc ReadLine {sock} {
 upvar #1 row rtemp; #Recognizes row.
 set r rtemp($sock)
 lappend $r "new"

 set line "Content-length: 247899"
 if { [string first "Content-length:" $line] == 0 } { lappend $r [string trim [string range $line 16 end]] }

 lappend $r "help"
 puts [lindex [set $r] 3]; #Output is: 247899.
 puts $rtemp($sock); #Output is: 1 GET new 247899 help.
}
    
proc do {sock} {
  set row($sock) {1 GET}
  ReadLine $sock
}

set sock 1
do $sock
 

Комментарии:

1. IIRC, обработчик событий канала не имеет стека вызовов (или, скорее, это первый уровень в новом), поэтому upvar с чем угодно, но #0 вряд ли будет полезен.

2. @Шон, спасибо тебе за объяснение.

Ответ №1:

Вы можете использовать upvar , если хотите, но вы обнаружите, что единственный кадр стека, видимый над обратным вызовом, — это глобальный кадр стека. Это как если бы его вызывали с uplevel #0 помощью . Он не может видеть внутренние процедуры, и если вы немного подумаете об этом, причина очевидна: нет никакой гарантии, что какая-либо конкретная процедура будет находиться в стеке, когда произойдет вызов.

Если вам нужно передать состояние из места, где вы устанавливаете обратный вызов, туда, где он вызывается, самые простые способы включают либо использование сопрограммы (которую можно возобновить из фактического глобального обратного вызова), либо использование объекта TclOO (который, конечно, может инкапсулировать все виды сложного состояния). В 8.5 и ранее вы более ограничены и должны использовать глобальные переменные/пространство имен, что может привести к беспорядку.


Чтобы ваш доступ к переменной работал, используйте upvar для создания псевдонима переменной:

 # upvar 0 is special in that it aliases a currently-visible variable
upvar 0 row($sock) r
puts [lindex $r 0]
 

Единственное реальное ограничение заключается в том, что вы не можете помещать массивы внутри массивов. Это было удалено давным-давно, потому что оно было сложным в использовании и приводило к сбоям.

Комментарии:

1. Спасибо вам за объяснение. Я не понимал, что #0 можно использовать без использования global . Поскольку я больше работаю, используя множество небольших процедур, их кажется трудным использовать upvar в качестве средства изменения одной и той же переменной. Например, если использовать его для ссылки state ReadLine и хотите позвонить CloseSock оттуда, что выполняется unset state($sock) , он, по-видимому upvar , больше не может использоваться для ссылки state . Глобальная переменная кажется проще, но я читал, что глобальные переменные не следует использовать. или используется минимально. Это правда?