Programación General > Visual Basic 6.0 e inferiores
Soporar usuarios recurrentes en un Sistema VB6
lagunax:
No hay problema Nebire, estas ayudandome bastante con esto, así que puedo esperar el tiempo necesario.
Una vez más gracias por todo tu apoyo.
:beer:
Nebire:
Hola Lagunax...
He decidido cambiar de estrategia, exclusivamente por razones de tiempo, por tanto en vez de exponérte el proyecto en diferentes fases te lo expondré por partes. El caso es equivalente y el fin el mismo que lo entiendas... la ventaja de la idea anterior es que verías como sufre la transformación en cada fase, pero ello me supone dedicarle más tiempo del que quisiera.
Mientras termino (no encuentro tiempo contínuo, para darle el empuje final), iré exponiendo las partes terminadas para que vayas dando bocados...
El código de archivo se expondrá siempre en un mensaje nuevo, aunque luego en el mismo se refiera a otros.
================ 1 Presentación y módulo de arranque ======================
El proyecto se compone de 5 clases, 3 formularios y 2 módulos. Por tanto digamos que se compone de 10 partes aunque se desarrolle en más o menos mensajes.
Para empezar qué mejor que el código del módulo de arranque. Este es muy breve, por tanto luego
El proyecto arranca desde una rutina MAIN, por tanto crea una aplicación nueva para albergar el proyecto, no hagas cambios a éste proyecto, usa una copia para hacer los cambios que desees en áquel, este lo dejas como referencia.
Este código es muy breve, aparte de la sub main, lleva una enumeración y un par de variables. Los detalles explicativos debajo del código.
--- Código: Visual Basic --- ' para investigar la razón de cierre y permitir o denegar si hay transacciones pendientes.Public Enum enCierrePrograma CIERRE_NO_DEFINIDO = -1 CIERRE_USUARIO_USA_BOTONES = 0 CIERRE_CODIGO_UNLOAD_FORM = 1 CIERRE_FIN_SESION_WINDOWS = 2 CIERRE_ADMINISTRADOR_TAREAS = 3 CIERRE_FORM_FORZADO_FORMMDI = 4 CIERRE_FORM_FORZADO_CIERRE_PADRE = 5End Enum Public RazonCierre As enCierreProgramaPublic Transa As cTransa Public Sub main() frmPrincipal.Show (1) ' cuando se cierre el form cliente finaliza la aplicación. Set Transa = Nothing EndEnd Sub
La enumeración 'enCierrePrograma' y la variable 'RazonCierre' operan en conjunto con las rutinas queryUnload de los formularios. La idea es determinar si hay transacciones pendientes y permitir o no el cierre, para evitar usos indebidos (se expondrá) en su momento un cierre real con transacciones pendientes, se podrá o no ocultar el formulario cliente, de modo que los usuarios 'patosos' no insistan y produzcan una pérdida de datos. Los detalles de esta enumeración se harán cuando se explique el formulario cliente.
La variable cTransa es el sustento del servidor. Cuando se ejecuta main, se abre un formulario es el formulario del administrador del servidor. Este es bastante simple, tiene 2 botones para iniciar y detener el servidor y opciones de comportamiento del servidor.
Cuando se abre el formulario principal se crea una instancia de la clase cTransa. Transa es esa instancia, Transa engloba toda la funcionalidad del servicio, el control de la misma por parte del administrador se realiza a través de ese formulario principal. El
Con el objeto de probar y simular los clientes, se crean desde el formulario del servidor instancias del cliente, una matriz (para el caso). En el caso real deberás modificar este aspecto para que un cliente remoto encienda el servidor si está apagado. Esto sería tan sencillo como añadir un procesado de command$ en este módulo de arranque. Este módulo lo he dejado muy breve porque será quien deba absorver la mayor parte de los cambios que realices a la aplicación. Siendo breve te será fácil modificarlo sin que el cóodigo existente suponga un obstáculo. de hecho incluso la enumeración podrías moverla a otro módulo que se expondrá más adelante que simplmenete recoge los tipos, con lo que sólo te quedarían 1 decena de líneas de código.
El presente proyecto se ha concebido como un servidor con múltiples clientes 'insitu', es decir no son remotos. La razón es que, lo que se trata de exponer es la interoperatividad asíncrona de los clientes con el servidor (que es lo que pedías), ...y no la conexión.
El presente proyecto se ha concebido con uso de datos definidos por el usuario (estructuras) frente al uso de base de datos. La razón es la misma que la anterior, facilitar la explicación del funcionamiento sin que interfiera los problemas derivados de la inadecuada conexión, errores de instalación etc... de la base de datos. (es un flaco favor explicar el funcionamiento de un motor de 4 tiempos, con un motor real de modo que al tratar de aflojar los tornillos para abrir el motor, estos no ceden y el tiempo se malgasta en encontrar las herramientas para solucionar un problema ajeno al problema inicial, que era pongamos mostrar la forma y localización de las válvulas).
Al caso debe entenderse que una estructura es muy parecido a una tabla. Es más soy de la opinión que las bases de datos aparte de tener tablas de datos deberían tener también tablas de funciones, de hecho te animo a que lo implementes. Crea una copia de tu aplicación y la base de datos para hacer cambios, y no toques la original. ..en otro momento volveremos sobre esto, si al término de la conversación (no de este u otro mensaje), se me olvidare, recuérdamelo con un quote de este párrafo...
En el siguiente mensaje en vez de exponer el formulario principal, expondré una clase básica. Las razones se explican allí...
Nebire:
================= 2 clase Base =====================
La clase base es una clase que implementa un timer y una colección y se le han añadido algunos métodos y eventos.
Para ser honestos, sería mejor convertir esta clase en una interfaz, por razones de rendimiento. Sin embargo eso me supone escribir más código... por lo tanto queda como ejercicio para el interesado, (si desea reconvertirlo9.
Esta clase es una clase cuya finalidad es ser instanciada, de hecho 3 clases que se expondrán en otros apartados son implementaciones de esta clase con los añadidos pertinentes (razón de más para que esta fuera una interfaz).
He preferido exponer esta clase antes que el formulario principal, por 2 razones, la 1ª es que parte de la funcionalidad de aquel no se entendería sin antes exponer esta clase (y otra) y la 2ª es que el formulario tiene más cuenta exponerlo como archivo adjunto para no tener que detallar las propiedades, los controles usados, etc...), aunque el código igualmente se expondrá para ser explicado.
Sin más, expongo el código y paso a las explicaciones (antes de entrar en las explicaciones pienso enviar el mensaje y luego reeditarlo, por lo que si lees este mensaje y debajo del código no ves explicaciones deberás esperar a que termine de editarlo.)
--- Código: Visual Basic --- ' Esta clase es una CÁPSULA que engloba una colección y un timer' la colección admite la posibilidad de limitar cantidad de elementos.' el timer se reparte en 2 eventos, y se ha añadido una función de consulta...' Toda la funcionalidad es común a las 3 clases, es por ello que se unifica en una sola clase' y las instancias exponen sólo las diferencias específicas de cada una. Private WithEvents s_Crono As TimerPrivate s_Coleccion As CollectionPrivate p_TamañoCola As Long ' determina si hay un límite máximo en cola de entrada, y si lo hay lo fija en un tamaño.Private p_LimitarCola As BooleanPrivate p_PausadoEntrantes As BooleanPublic Condicion As Boolean ' condicion para servir elemento ' ambos eventos los provee el timer...' sólo salta el evento si la colección tiene eventos y no se cumple la condiciónPublic Event Crono()' sólo salta el evento si la colección tiene elementos y se cumple la condición.' la condición debemos activarla cuando queramos que nos devuelva el primer elemento.Public Event ServirElemento(ByRef Elemento As Object) Private Sub Class_Initialize() p_LimiteCola = 0 ' indica que no hay límite de transacciones de entrada. p_LimitarColaSolicitud = False Set s_Coleccion = New Collection p_PausadoEntrantes = False Condicion = FalseEnd Sub ' debe invocarse esta función justo tras ser creada la clase.Friend Function SetTimer(ByRef Tim As Timer) As Boolean On Local Error Resume Next Set s_Crono = T s_Crono.Interval = 1000 s_Crono.Enabled = False SetTimer = (s_Crono Is Nothing)End Function Private Sub Class_Terminate() If Not (s_Crono Is Nothing) Then s_Crono.Enabled = False Set s_Crono = Nothing End If Set s_Coleccion = NothingEnd Sub ' PRECAUCIÓN: sólo debería usarse en la instancia de solicitudes.' o incluso en la instanacia llamada de ejecución, pero nunca en la siguiente.' se debe recordar que funcionan en cascada...Public Property Get PausaEntrantes() As Boolean PausaEntrantes = p_PausadoEntrantesEnd Property Public Property Let PausaEntrantes(ByVal p As Boolean) If p_PausadoEntrantes <> p Then p_PausadoEntrantes = p End If End Property ' pausa, pausa tanto entrantes como salientes....Public Property Get Pausa() As Boolean Pausa = Not s_Crono.EnabledEnd Property Public Property Let Pausa(ByVal p As Boolean) If p_Pausado <> p Then p_PausadoEntrantes = p s_Crono.Enabled = Not p End If End Property Friend Property Get Intervalo() As Integer Intervalo = s_Crono.IntervalEnd Property Friend Property Let Intervalo(ByVal x As Integer) s_Crono.Interval = x End Property ' el timer provee 2 eventos, ambos sólo si la colección tiene elementos.' Se sirve el primer elemento siempre que se cumpla la condición.' la condición es un valor buleano que asigna la instancia de esta clase.' es allí donde se decide cuales son las justificaciones de la condición.' puesto que cada instancia tendrá condiciones distintas, lo único común es' precisamente, definir que se 'cumplió la condición' ... de este modo es común a todas las instancias.' cuando no se cumple la condición se genera el evento crono, que ofrece la oportunidad de evaluar si se cumple o no la condición.' es allí donde debe establecerse si se cumple o no la condición.' Dado que se ignora cuando se incumple la condición para cada instancia' debe decidirse en cualquiera de los 2 eventos el caso que no, o podría activarse la línea comentada' en cuyo caso sólo se puede decidir si se cumple la condición en el evento crono... dividiendo la frecuencia de entregas...Private Sub s_Crono_Timer() If s_Coleccion.Count > 0 Then If Condicion = True Then Dim it As Object Condicion = False Set it = s_Coleccion.Item(1) RaiseEvent ServirElemento(it) ' se elimina desde la clase que lo añade a otra colección, para hacerlo compatible con ' la nueva propiedad añadida a última hora: PausarEntrantes 'Call s_Coleccion.Remove(1) Set it = Nothing Else RaiseEvent Crono End If End IfEnd Sub ' es una propiedad de administrador' si se establece el valor a true, no se podrán añadir elementos mientras' el nº de elementos en la colección sea mayor= que el tamaño de cola.Public Property Get LimitarCola() As Boolean LimitarCola = p_LimitarColaEnd Property Public Property Let LimitarCola(ByVal lc As Boolean) If lc <> p_LimitarCola Then p_LimitarCola = lc End If End Property ' es una propiedad de administrador' esta propiedad sólo indica en tamaño de cola, pero quien determina si se limita o no, es LimitarcolaPublic Property Get TamañoCola() As Long TamañoCola = p_TamañoColaEnd Property Public Property Let TamañoCola(ByVal lm As Long) If lm > p_TamañoCola Then p_TamañoCola = lm End If End Property Public Property Get Count() As Long Count = s_Coleccion.CountEnd Property ' lo hemos puesto variant poque igual puede pedirse por índice que por keyPublic Property Get Elemento(ByVal Index As Variant) On Local Error Resume Next Set Elemento = s_Coleccion.Item(Index)End Property Friend Property Set Elemento(ByVal Index As Variant, ByRef it As Object) On Local Error Resume Next Set s_Coleccion.Item(Index) = itEnd Property ' ni añadir ni eliminar se dejan públicas, ya que sólo una clase añade a otra.Friend Function Añadir(ByRef T As Object, ByVal Key As String, Optional Despues As Boolean = False, Optional DetrasDe As Long = 1) As enAñadiendo If p_PausadoEntrantes = False Then If p_LimitarCola = True Then If s_Coleccion.Count >= p_TamañoCola Then Exit Function End If On Local Error Resume Next Err.Number = 0 If Despues = False Then Call s_Coleccion.Add(T, Key) Else ' esta versión sólo la utiliza la clase de solicitudes... Call s_Coleccion.Add(T, Key, after:=DetrasDe) End If If (Err.Number = 0) Then Añadir = ADD_AÑADIDO Else Añadir = ADD_ERROR_COLECCION ' se supone que no puede ser: ADD_ERROR_DATOS, se hubiera detectado en la llamada Add_Transaccion de 1ª fase...(cSolicitudes) End If Else Añadir = ADD_PAUSADO End IfEnd Function ' lo normal es eliminar siempre el elemento 1º, las s_Colecciones están basadas en ínndice 1' no usamos esta función en la práctiva, se provee para futuras necesidades...Friend Function Eliminar(Optional Posicion As Long = 1) As Boolean On Local Error Resume Next If Posicion > 0 Then Err.Number = 0 s_Coleccion.Remove (Posicion) Eliminar = (Err.Number = 0) End IfEnd Function Public Function Consulta_MisPedidosEnCola(ByRef IdRef As Form) As Long Dim n As Long, k As Long Dim tr As infoTransaccion On Local Error Resume Next For k = 1 To s_Coleccion.Count Set tr = s_Coleccion.Item(k) If tr.Control.IdRef.Name = IdRef.Name Then n = n + 1 End If Next Set tr = Nothing Consulta_MisPedidosEnCola = nEnd Function
Esta clase la hemos llamado cBase.
Como se dijo al principio, el objetivo de esta clase es encapsular funcionalidad que vamos a reutilizar en diferentes clases. La funcionalidad se detalla a continuación:
Se provee servicio para un timer, puede modificarse su propiedad enabled e interval, enabled es rellamada como pausa (y su valor es not enabled ara que guarde sentido), interval se renombra a intervalo.
Puesto que el timer se relaciona con un objeto collection, se provee una propiedad que media entre pausa del timer y el acceso a la colección que se llama PausaEntrantes .
Cuando la clase se inicializa, se crea una nueva instancia de un objeto collection sobre la variable Coleccion. La colección expone los métodos típicos de la colección Add=Añadir, Remove=eliminar, Count=Count, Item=elemento, pero además se le dota de 2 propiedades más: LimitarCola y TamañoCola así mismo se le añade una función de consulta para búsqueda: Consulta_MisPedidosEnCola
Como decía al inicializar la clase se crea una nueva instancia de colección, y se establecen valores iniciales por defecto a las propiedades.
Tras ser inicializada la clase, necesita una 2ª inicialización (vb6 no permite parámetros al crear clases), esta 2ª inicialización se lleva a cabo sobre la función SetTimer. en dicha función se envía un timer, porque como todo el mundo debe saber las clases no pueden crear objetos (una clase no es un objeto, estrictamente hablando (en VB&) aunque para nuestro efectos es como si lo fuera pues se comporta casi como tal). Sin embargo que no se puedan crear objetos no implica que no pueda albergar y manipular objetos (de interfaz fráfica, por ejemplo un Command, Option, Picturebox... o un Timer).
Esta 2ª inicialización establece valores iniciales al timer que son distntos de los que quedan establecidos en un timer cuando se crea, esto debe tenerse en cuenta para cambiar más adelante estos valores a los deseados. Si la función no recibió un timer devolverá falso, como esto no es deseable este resultado se utilizará para saber si el servidor estará dispnible o no, es más, si devuelve false, el servidor no se 'encenderá'.
El detalle de como recibe el timer, se explica en otro apartado.
Los métodos de la colección han sido rescritos para acoplar la actuación del timer (entre otras cosas), por ejemplo: si la propiedad PausaEntrantes, se establece a True, la función añadir queda 'bloqueada'. Por ejemplo la función Eliminar por defecto elimina siempre el primer elemento de la colección. Count, por ejemplo no se rescribe, pero como podemos desear tener acceso a dicha propiedad de la colección, debemos exponerla. Naturalmente exponer nuestro métodos rescritos (o no) supone que la colección la hemos designado privada, para que no se puedan invocar directamente los métodos Add, Remove, etc... sin pasar previamente por nuestra funcionalidad...
La función añadir, por defecto añadirá elementos al final de la colección (es lo más frecuente), pero como hemos establecido prioridades en las peticiones de los clientes, existe la probabilidad de que se añadan en otra posición, la instancia (de esta clase) que recibe las solicitudes se encarga de gestionar la prioridad, y es desde allí que se señala el punto de inserción en la colección. el resto de instancias de esta clase siempre añaden al final de la clección.
El método eliminar, en teoría siempre debería eliminar el primer elemento en la colección, pero se ha establecido la posibilidad de que pueda, eliminarse un elemento en cualquier posición dentro de la colección, para atender posibles futuros cambios, en el proyecto actual no se utiliza.
La propiedad PausaEntrantes, permite al administrador pausar el servicio, (lo que impide que los clientes puedan añadir mientras eestá en pausa añadir solicitudes) para cualquier tarea que necesite realizar. Este modo de pausa, sin embargo no detiene el trabajo del servidor, pués sigue gestionando las transacciones almacenadas. También si se dessea puede programarse para que en determinadas situaciones se produzca dicha pausa desde código... Esta propiedad, debe usarse con cuidado, pués el servicio lo que hace es impedir añadir solicitudes en la fase pausada en este modo, desde la fase anterior... en cada instancia, respectivamente se comenta en detalle la implicación de usar esta propiedad.
La propiedad Pausa,si se establece a true (timer.enabled=false) realmente detiene la instancia, es decir no sirve solicitudes a la siguiente fase, ya que de eso se encarga el timer, que está desactivado. Esta propiedad tiene repercusiones parecidas a la anterior, y el cambio de esta comporta obligadamente el cambio en la anterior. La idea es pausar tanto los añadidos como los envíos a la siguiente fase. Esto puede tener su importancia por ejemplo para consultar el estado real de las transacciones, con fines a verificar el comportamiento, realizar estadísticas,etc... por ejemplo cada 10 minutos podrían pausarse todas, y elaborar un informe de cuantas transacciones tiene cada colección, los valores de sus intervalos,etc.. y luego restablecer el servicio. Esto apenas duraría menos de 1 segundo, y sería util para estudiar y afinar el comportamiento del servidor.... Naturalmente puede utilizarse para razones administrativas diversas, estoes sólo una propuesta de ejemplo.
La propiedad Intervalo, es importante ya que cada instancia deberá tener su valor específico y será más o menos difícil encontrar un balance entre todos que sea el más óptimo, es posible que determinadas peticiones requieran un ajuste específico de cada uno de los tmers. En un caso real debería establecerse casos particulares con valores de configuración específicos, ya probados y demostrados como más efectivos para tipos determinados de transacciones. Por ejemplo, supongamos que el servidor se enciende a las 8 de la mañana y se apaga a las 6 de la tarde (para los clientes, pero hay un cliente administrador que al término del día realiza unas labores determinadas), es posible que lo primero que hagan los clientes sea casi exclusivamente solicitar datos, a medida que pasen las horas es probable que las peticiones estén repartidas entre solicitar datos y guardar datos, y posiblemente hacia el mediodía decaigan tanto unas como otras, en ese momento podría aprovecharse para hacerse gestiones internas de mantenimiento, y en definitiva cada tipo de operaciones tiene un comportamiento que podría optimizarse fijando configuraciones específicas demostradas eficaces...
El timer con su evento, provee 2 eventos a las instancias, que se sirva un evento u otro depende de que se cumpla una condición. Puesto que la condición lóogicamente cambiará en cada instancia, el modo de tiodas las instancias tengan una condición común es que esta tenga un valor reconocido por todas, y cuyo valor lo especifique la propia instancia. Así si no se cumple la condición se devuelve el evento crono (que es el equivalente al propio 'elapsed' _Timer, del Timer), durante ese evento se evalúa en la instancia si debe cambiar la condición a True o no. Si la condición se estableció a True desde la instancia entonces se genera en el siguiente ciclo el evento servirElemento, que proporciona un parámetro: el primer elemento de la colección. El consumidor del evento (la instancia) en el tratamiento de ese evento tratará de añadir dicho elemento a la siguiente fase... el resto de los detalles es específico de dichas clases que se explicarán en su debido momento.
Hay que recalcar un detalle, al proporcionar los eventos de esta manera, sucede que la frecuencia de trabajo del timer la estamos diviendo entre '2', ya que en un ciclo evalúa si la condición es false y se establece entonces la condición a true, necesita como mínimo otro ciclo para devolver el elemento desde la condición true... es decir esto influye en el intérvalo real en que se servirán los elementos. Hay 2 alternativas, la más obvia es reducir el intérvaloa la mitad, el otro (que debe estudiarse si interesa o no) es modificar el código como se muestra en este ejemplo alternativo:
--- Código: Visual Basic --- Private Sub s_Crono_Timer() If s_Coleccion.Count > 0 Then If Condicion = True Then Dim iT As Object Condicion = False Set iT = s_Coleccion.Item(1) RaiseEvent ServirElemento(iT) Set iT = Nothing End If RaiseEvent Crono End IfEnd Sub
Como se ve, en este modo alternativo, tras enviar un elemento por una condición True, acto seguido se envía también el ecento crono para restablecer la condición si fuera necesario, no hay por tanto pérdidas de ciclo. Cada cual debería establecer cual método le interesa más y cambiarlo si procede. Si la clase cBase (esta) fuera una interfaz, cada implementación (las instancias actuales de esta clase) podría optar concretamente por la alternativa específica más eficaz al caso.
La propiedad TamañoCola determina el tamaño que puede asignarse a la colección ( la colecciones están basadas en valores long, como límite de sus elementos). Esta propiedad puede usarse como control de desbordamiento simplemente o como se desee. Por defecto el valor se establece a 0 es decir si no se cambia y se alcanzara el límite de 2^31 -1, ocurriría un error y no se podría añadir a la colección. Por último, decir que esta propiedad sólo establece el tamaño de elementos que admite la colección, pero el establecimiento de bloquear añadir elementos tras alcanzar (o si se superó) el n´mero de elemntos en la colección es la siguiente porpiedad...
La propiedad LimitarCola, determina si se actúa en consecuiencia con un tamaño límite de cola. Debe notarse que si la colección tiene un número de elementos y se establece un valor más bajo y luego está propiedad (limitarcola se establece a true), no se eliminan elementos de la colección, (nunca), sólo impide que se añadan más. Aún podría crearse una propiedad que se llamara AutoLimitarCola, de tal modo que tras alcanzar (o si se habia superado previamente) el tamaño de la cola, se desactivara y con ayuda de otra propiedad de ResetLimitarCola que específica un tamaño de cola menor que Tamaño cola volver a añadir elementos. Un ejemplo: si Count = 128 elementos y el TamañoCola lo establecemos a 100 y ResetLimitarCola =60, significa que al establecer la propiedad AutoLimitarCola, no dejará añadir elementos hasta que count=60 y se volvería a bloquear cuando Count=100. Desarrollar estas propiedades y adaptarlo queda como ejercicio para el interesado...
La función Consulta_MisPedidosEnCola, permite reconocer cuantos elementos de un cliente existen actualmente en esa colección. Esta función es síncrona por lo que si la colecciones alcanzan gran volumen de elementos, podría significar un retraso, por tanto debe usarse con precaución y lo menos posible. Podría y debería crearse una función en la clase cTransa que determine el esado de una transacción de un cliente, bsándose en que colección se encuentra y que orden ocupa en dicha colección, si se proporciona un medio que calcule el promedio de tiempo en servir una transacción se podría valorar un tiempoestimado en resolver la transacción por la que un cliente solicita... para no complicar el código, se han evitado funciones interesantes y útiles pero que son ajenas a resolver el problema planteado.
Lo que respecta a esta clase ya se ha explicado.
Nota: no se descarta que hubiera algún fallo o alguna imprecisión en las explicaciones pués en este momento la clase aún no ha sido probada y es posible que se me haya escapado algún gazapo....
Por hoy vale.
p.d.: debe notarse que la mitad del código lo componen líneas en blanco y comentarios...
Nebire:
Mañana expondré otra parte... sin embargo espero alguna intervención de tu parte, de lo contrario consideraré que te has olvidado del asunto... ...lo que menos me agrada es perder el tiempo... ...para nada.
Expón dudas si las tienes, preguntas si te quedan, o, lo que sea, pero el foro si no es interactivo es una tarea y por ahí no se cede...
Navegación
[*] Página Anterior
Ir a la versión completa