|
libThr v2.70.058, Copyright © El Zooilógico, 2.000
Descripción
libThr posibilita la ejecución de un máximo de 99 procesos concurrentes. Para este propósito, expone una interfaz COM, ThreadInterface, que facilita la creación de objetos OLE/COM en hilos de ejecución independientes. Cualquier objeto que soporte automatización puede ser creado por medio de la funcionalidad expuesta por libThr.
libThr soporta tanto notificación síncrona como asíncrona de valores de retorno; al invocar un método de un objeto creado mediante la interfaz expuesta por libThr, puede esperar el resultado indefinidamente hasta que éste se produzca, o retornar inmediatamente y comunicarlo por medio de un evento en cuanto el mismo esté disponible.
Asímismo, libThr soporta lo mismo procesos de modelo en apartamento ‘single-threaded Apartment’, como modelos multihilo ‘free multi-threaded’; puede ejecutar múltiples objetos de un único modelo (bien sea apartamento o libre) o un mezcla de ambos. Cada nuevo objeto solicitado, será inicializado acorde al modelo que haya sido definido para el mismo. Esta operación es interna a la librería y totalmente transparente para el desarrollador.
libThr es una librería ‘in-proc’, esto es, su código corre en el hilo primario del proceso que solicita una instancia de la misma. Su uso esta enfocado principalmente para la utilización simultánea de múltiples objetos OLE, bien sea, para acceder a múltiples instancias de objetos ya existentes o para crear sus propios servicios y librerías.
Puede centranse en la implementación del objeto en sí, y olvidarse de los detalles de sincronización, planificación, inventariado... dejando que libThr se ocupe de ello.
Breves notas sobre aspectos a destacar
Usar ThreadInterface
Para hacer uso de la funcionalidad de ThreadInterface, debe agregar a su proyecto la librería de tipos de libThr. Por ejemplo, en VB, abra la opción Referencias, en el menú Proyecto, y seleccione la referencia ‘libThr 2.7 type library’. Ahora podrá crear instancias de la interfaz ThreadInterface.
Declare un objeto derivado de ThreadInterface
Public miThreadObjeto as new ThreadInterface (o bien) Public WithEvents miThreadObjeto as ThreadInterface ... ... Set miThreadObjeto = new ThreadInterface
NOTA: La segunda declaración habilita la recepción asíncrona de eventos, mientras que la primera no.
Proceso de creación de hilos
Una vez haya creado una instancia de ThreadInterface, puede invocar el método CreateThread cada vez que necesite un nuevo hilo. Los nuevos hilos son generados siguiendo el siguiente modelo:
a. en primer lugar, se crea un nuevo hilo de ejecución
b. dentro ya del nuevo hilo se crea una instancia del objeto solicitado
c. el hilo, dependiendo de los parámetros de creación, queda a la espera de mensajes que procesar (hilo no recursivo), o bien llama constantemente al método solicitado (hilo recursivo) comprobando de manera previa a cada nueva iteracción la presencia o no de otros mensajes que procesar. En ambos casos, el hilo permanece activo hasta que se le ordene terminar.
d. se libera la referencia del objeto
e. finalmente, se elimina el hilo.
Hilos recursivos/no recursivos
Un hilo no recursivo es pasivo, esto es, permanece inactivo a la espera de mensajes que procesar. Cuando recibe un mensaje, realiza un ciclo completo para procesar el mismo y queda a la espera de nuevos mensajes.
Un hilo recursivo, por el contrario, es un hilo activo; procesa mensajes del mismo modo que un hilo no recursivo, pero en ausencia de ellos no permanece a la espera, sino que invoca constantemente el método predeterminado.
Suponga una función que compruebe la presencia de datos en un puerto de comunicaciones durante un intervalo concreto. Una vez finalizado el intervalo (o ante la recepción de datos), la función comunica la presencia o no de datos y devuelve los mismos o no.
Este es el tipo de función adecuada para ser invocada desde un hilo recursivo asíncrono. Éste invoca al método, y en función del resultado, comunica al proceso padre la presencia o no de datos. Así, indefinidamente, mientras no reciba un mensaje solicitando otra tarea. El proceso padre, queda liberado de realizar una comprobación periódica del puerto, debiendo atender únicamente las notificaciones que reciba desde el hilo secundario.
Los hilos recursivos, procesarán siempre primero los mensajes recibidos; sólo en ausencia de mensajes invocarán el método por defecto. De este modo, los requerimientos del proceso padre serán atendidos de manera preferente.
Los hilos no-recursivos, realizan un muy eficiente tiempo de espera, por lo cual consumen un casi nulo tiempo de procesador. Así, son indicados para tareas no periódicas, como podría ser leer los datos del puerto de comunicaciones del ejemplo anterior, cuya presencia conocemos gracias a la notificación del hilo recursivo.
Los hilos secundarios disponen de una cola de mensajes, de manera que los mensajes que no puedan ser atendidos en el momento (porque se esté ejecutando un método externo) sean procesados siguiendo la misma secuencia en que fueron notificados. Puede configurar el valor máximo permitido de mensajes perdientes entre un mínimo de 4 o máximo de 32. Todo nuevo mensaje notificado a un hilo que haya alcanzado el máximo de mensajes en espera será rechazado hasta que el número de mensajes pendientes haya disminuido por debajo del valor máximo indicado para el mismo. En este caso, se devolverá un código de error interceptable. Por defecto, el valor máximo de mensajes en espera es de 15.
Notificaciones síncronas/asíncronas
Cuando invoque un método de un objeto creado que corre en un hilo secundario, si éste ha de retornar un resultado, o bien esperamos la presencia del mismo (modelo síncrono), o bien indicamos que nos sea comunicado en cuanto se produzca y retornamos inmediatamente (modelo asíncrono).
Cada modelo tiene sus ventajas. El modelo asíncrono, libera al proceso padre de los tiempos de espera, pudiendo dedicarse a otras tareas. El modelo síncrono, a la espera del resultado no puede realizar otra tarea, pero es adecuado cuando la presencia del mismo es imprescindible para continuar (procesar un cálculo, o forma parte de una comprobación, por ejemplo).
En ambos modelos, la ejecución del metodo externo se realiza en el hilo secundario; he de resaltar, sin embargo, que mientras en el modelo síncrono el procesamiento del valor de retorno se produce en el proceso principal, en el modelo asíncrono el procesamiento se realiza también en el hilo secundario. De este modo, si la respuesta al resultado de un objeto es, pongamos por caso, visualizar una ventana modal que muestre el mismo, en el modelo síncrono, hasta que no se cierre la misma el proceso queda detenido, pero en el modelo asíncrono aparecerá esa misma ventana (o muchas simultáneamente), sin afectar al proceso principal.
Si mantiene varios procesos síncronos y asíncronos simultáneamente, sólo uno de los síncronos podrá mostrar la ventana modal, quedando los demás a la espera de que el proceso principal esté disponible, mientras que los asíncronos pueden mostrar cada uno una ventana, independientemente del estado del proceso principal, incluso de manera simultánea a una ventana síncrona. Es más, todo ello a través de la misma sección de código de su programa.
La elección de un modelo u otro dependerá de la tarea que deba realizar un hilo secundario, y no puede ser modificado una vez definido. Aún así, en situaciones puntuales, puede provocar el comportamiento inverso indicando intervalos máximos de espera (síncrono a asíncrono), o forzando devolución de llamada (asíncrono a síncrono).
NOTA: Para recibir notificaciones asíncronas debe habilitar la recepción de eventos. Desde VB, debe indicar la palabra clave WithEvents al instanciar la clase ThreadInterface.
Dim WithEvents miThreadObjeto as ThreadInterface
Asi, dispondrá de dos eventos que verá (de nuevo, en VB) como:
miThreadObjeto_AsyncRetval(nThreadID as long, retval as variant) miThreadObjeto_GotCOMError(nThreadID as long, nErrorID as long)
AsyncRetval, será el que reciba las notificaciones asíncronas, recibiendo al mismo tiempo los valores de retorno (si éstos se han producido). GotCOMError, recibirá los errores de invocación de métodos OLE/COM.
El funcionamiento de las notificaciones asíncronas está condicionado según los valores que indique en el argumento flags de la función CreateThread. Consulte la descripción del mismo para más información.
Destacaré entre ellos, el bit de funcionamiento protegido. Las notificaciones asíncronas pueden ser protegidas o no protegidas: en el primer caso, las notificaciones se realizarán una a una, impidiendo nuevas llamadas mientras una esté en proceso. Si las llamadas acceden a datos sensibles a ser manipulados por varios procesos al mismo tiempo (una variable global, por ejemplo), deberá habilitar la protección para evitar la corrupción de los mismos (vea nota). Si la protección está deshabilitada las notificaciones se realizarán independientemente del número de llamadas en proceso. Si estas llamadas operan con valores no sensibles como variables locales, valores de retorno, arrays indexados o incluso variables globales (siempre y cuando sean de sólo-lectura), puede deshabilitar la protección. En el primer caso, obtiene más seguridad; en el segundo mayor rendimiento. La elección de un modo u otro dependerá de como planee procesar los valores de retorno.
Tipos de invocación
Al llamar a los métodos, CreateThread, StopThread y Exec, puede opcionalmente indicar el nombre de una función del objeto a la que desee invocar. En este caso, deberá asímismo indicar el tipo de invocación a realizar para la función solicitada.
Los métodos expuestos por objetos COM, son invocados de acuerdo a la funcionalidad que implemetan. Principalmente, son los siguientes:
1, Método (DISPATCH_METHOD), el método invocado es una función; ésta puede o no retornar un valor.
2, Propiedad, (DISPATCH_PROPERTYGET). El método invocado devuelve el valor de una propiedad del objeto.
4, Propiedad, (DISPATCH_PROPERTYPUT). El método invocado establece una propiedad del objeto, al valor indicado.
8, Propiedad, (DISPATCH_PROPERTYPUTREF). Igual que el anterior, pero el valor es pasado, por referencia.
Cuando indique una función, deberá indicar el modo de invocación (1,2,4 u 8), para que ThreadInterface haga la llamada de manera correcta.
libThr, podría identificar el tipo de invocación necesaria, pero esto supone cargar la biblioteca de tipos de la librería que expone el objeto e identificar la interfaz requerida para, posteriormente, a cada nueva invocación, localizar el método y recorrer la descripción del mismo con el fin de averiguar el tipo de invocación que éste espera.
La biblioteca de tipos es un componente de la librería, no del objeto u objetos expuestos. En un entorno, en el que múltiples instancias de un objeto pueden intentar un acceso simultáneo a la biblioteca de tipos de la librería que los implementa, resulta poco eficiente tanto disponer de una descripción de la misma compartida por todos los hilos, como una copia exclusiva para cada hilo. En el primer caso, por razones de sincronización, en ambos, además a causa de la pérdida de rendimiento.
Es por ello que he decidido, no implementar esta funcionalidad.
El paso de argumentos
Los métodos expuestos por ThreadInterface requieren algunos parámetros obligatorios, y/o otros opcionales. Entre los opcionales, cabe diferenciar aquellos que toman valores por defecto, y aquellos que no lo hacen. En todo caso, todo parámetro opcional puede ser obviado, pues en su ausencia tomará su valor por defecto o será descartado. Así, son válidas las llamadas siguientes:
newThreadID = miThreadObjeto.CreateThread ('miDll.miClase') newThreadID = miThreadObjeto.CreateThread ('miDll.miClase', , , , , , , , nResult) newThreadID = miThreadObjeto.CreateThread _
('miDll.miClase', 5000, , , 4,'SetData', 'ValorInicial')
Puede prescindir de aquellos parámetros opcionales que no seán de interés, pero los indicados deben ir en la posición que les corresponde.
Los parámetros que sea necesario pasar a un método de un objeto OLE, deberá indicarlos en el parámetro params. Este es un tipo variant, que puede ser simplemente un variant o una matriz de variants. Si necesita pasar más de un parámetro, será un array de variants; si es un único parámetro, puede pasarlo como un array de un único elemento o como un variant indistintamente.
Así, por ejemplo:
miThreadObjeto.Exec (newThreadID, ,4 , ,'SetData', 'Valor') Dim varParams(3) varParams(0) = True varParams(1) = 'Esto es una cadena' varParams(2) = 2000 varParams(3) = 'Otra cadena' miThreadObjeto.Exec (newThreadID, True, 1, ,'Metodo_1', varParams, varRetVal)
Del mismo modo, que con las funciones de ThreadInterface, si el método de un objeto al que invoque dispone de parámetros opcionales y desea que tomen el valor por defecto, deberá dejar su posición vacía, para que ThreadInterface los coloque en la secuencia adecuada.
Dim varParams(3) varParams(0) = True varParams(2) = 3000 miThreadObjeto.Exec (newThreadID, True, 1, ,'Metodo_1', varParams, varRetVal)
NOTA: para evitar crear un array de variants de diferente dimensión cada vez que deba invocar un método con múltiples parámetros, puede crear una función que acepte un número variable de argumentos de tipo variant, y llamar a la misma con los parámetros que sea preciso, sin preocuparse del número de los mismos.
Public Sub Exec(ThrId as long, Funcion As String, Optional TipoInv as Integer, _
ParamArray() params)miThreadObjeto.Exec (newThreadID, , TipoInv, ,Funcion, params)End Sub
Ahora puede escribir, las llamadas anteriores como
Exec (newThreadID, 'SetData', 4, 'Valor') Exec (newThreadID, 'Metodo_1', 1, True, 'Una cadena', 2000, 'Otra cadena') Exec (newThreadID, 'Metodo_1', 1, True, , 3000)
Métodos
En la descripción del formato de los métodos expuestos por ThreadInterface, los parámetros entre corchetes indican parámetros opcionales. Si, además, aparece un valor, este será el valor que tomará por defecto en caso de no ser indicado.
PauseThread
Suspende la ejecución del hilo indicado hasta que sea resumido o eliminado.
Este método no retorna ningún valor.
Formatos (C & VB)
void PauseThread (long nThreadID)
PauseThread (nThreadID as long)
nThreadID, ID del hilo a suspender.
ResumeThread
Resume un hilo previamente suspendido.
No retorna ningún valor.
Formatos (C & VB)
void ResumeThread (long nThreadID)
ResumeThread (nThreadID as long)
nThreadID, ID del hilo a resumir.
ThreadPriority
Establece la prioridad del hilo al valor indicado en nLevel.
No retorna ningún valor.
Formatos (C & VB)
void ThreadPriority (long nThreadID, [long nLevel = 0])
ThreadPriority (nThreadID as long, [nLevel as long = 0])
nThreadID, ID del hilo.
nLevel, nuevo nivel de prioridad. Los valores admitidos son de 0 (mínima) a 4 (máxima), siendo 2 el valor por defecto del sistema (8).
CreateThread
Crea un nuevo hilo de ejecución para el objeto indicado en ThreadReference. Opcionalmente, puede invocar una función que se ejecutará en primer lugar en cuanto el hilo se ponga en marcha (por ejemplo para inicializar alguna propiedad del objeto). El hilo será creado conforme a los parámetros indicados en flags.
Si no hay error, o éste no es grave, CreateThread devuelve un entero largo que será la ID asignada al hilo que lo identifica. Deberá indicar esta ID cuando llame a las demás funciones de ThreadInterface. En caso contrario retornará 0, y colocará un código de error en nResult.
Formatos (C & VB) long CreateThread(LPCTSTR ThreadReference, [long WaitTimeout = 2500], [long flags = 3843], [LPCTSTR RecursiveFunction], [short invKind = 1], [LPCTSTR Function], [VARIANT *params], [VARIANT *retval], [long *nResult]) CreateThread(ThreadReference as string, [WaitTimeout as long = 2500], [flags as long = 3843], [RecursiveFunction as string], [invKind as integer = 1], [Function as string], [params as variant], [retval as variant], [nResult as long]) as long
ThreadReference, será una cadena que contendrá el ProgID del objeto a crear. Debe indicar el ProgID en la forma usual de descripción de clases de automatización, 'fichero dll o exe.clase a instanciar', como por ejemplo 'Excel.Application' o 'midll.miclase'.
flags, es un entero largo que será leído como un campo de bits, dividido en tres secciones: modo de funcionamiento (protegido/no protegido, síncrono-asíncrono, recursivo-no recursivo, etc), nivel de prioridad y número máximo permitido de mensajes pendientes, (vea más adelante).
RecursiveFunction, contendrá una cadena con el nombre del método del objeto a invocar de manera predeterminada por un hilo recursivo. Si no ha indicado la bandera de recursividad en flags, será ignorada. La función invocada, deberá ser necesariamente un método (tipo de invocación 1), (vea notas).
invKind, modo de invocación del método indicado en Function, (vea notas).
Function, cadena con el nombre de la función a llamar nada más sea creado el hilo. Esta función se invocará una sola vez y siempre en primer lugar.
params, parámetro o parámetros a pasar al método invocado, (vea notas).
retval, variant donde se devolverá el resultado.
nResult, entero largo que contendrá el resultado de la operación.
Códigos de error devueltos
0, ningún error. 1, se indicó bit de recursión, pero no 'RecursiveFunction'. El hilo se ha creado no recursivo. 2, se ha sobrepasado el tiempo de espera indicado, (vea apéndice A). 4, Sin memoria. No se invocará el método indicado en Function, (idem). 15, (hex. F), hay 99 hilos ejecutándose, no se crearán más. 16, (hex.10), fallo la creación del hilo, (normalmente, se ha indicado alguna operación asíncrona en flags pero no se ha instanciado la clase para realizar procesamiento asíncrono) (vea nota). 160, (hex. A0), el objeto solicitado no tiene un modelo multihilo válido. 176, (hex. B0), no se pudo inicializar COM para este hilo. 192, (hex. C0), el ProgID indicado no esta registrado como objeto automatizable. 208, (hex. D0), no se pudo crear instancia del objeto. 224, (hex. E0), el objeto no implementa la interfaz IDispatch. 240, (hex. F0), no se encontró en el objeto una función que corresponda a la indicada en RecursiveFunction.
Los códigos de error mayores de 15 (hex F), son notificaciones de error grave, el hilo no ha sido creado. Si son menores de 14 (hex E), son notificaciones de error leve; la creación del hilo ha sido satisfactoria, pero algún requisito no ha sido cumplido. Puesto que el proceso de creación sigue adelante, son agregados al valor retornado. Así por ejemplo, 193 (hex. C1), comunica que aunque se indicó flag de recursión, no se indicó nombre de funcion recursiva, y que el hilo no ha sido creado, porque el ProgId indicado no está registrado.
NOTA sobre configuración del campo flags
flags, es un entero largo, aunque se leerá como campo de bits dividido en tres secciones,
0000.0000.0000.0000.0000.0000.0000.0000 (los 16 bits más significativos se ignoran)
parámetros de funcionamiento (bits 0 a 4)
Bit 0, proteger llamadas asíncronas (1), no proteger (0). Por defecto, 1. (vea nota) Bit 1, notificar errores OLE (1), no notificar(0). Por defecto, 1. Bit 2, bit de recursividad. Hilo recursivo (1), no recursivo(0). Por defecto, 0. En este caso deberá indicar también 'Recursive function', si no el bit será borrado. Bit 3, habilitar notificación asíncrona (1), o síncrona (0). Por defecto, 0. Bit 4, notificación asíncrona habilitada también para función recursiva (1), o desabilitada(0). Por defecto, 0.
El bit 3, habilita la notificación asíncrona de cualquier resultado producido después de procesar un mensaje (hilos tanto recursivos como no recursivos) El bit 4, habilita además la notificación asíncrona de valores de retorno de la función predeterminada para los hilos recursivos. Si el hilo es no recursivo, se ignora.
nivel de prioridad del hilo (bits 5 a 7)
Indicará el nivel de prioridad con que se desea sea creado el hilo. Valores posibles 0 a 4. Por defecto 0. La prioridad puede ser normal (nivel 2), un punto por debajo (1), mínima (0), un punto por encima(3) o máxima (4). En todo caso, el nivel de prioridad puede ser modificado posteriormente, vea ThreadPriority.
Mensajes en espera (bits 8 a 15)
Indicará el número máximo de mensajes en espera permitidos en la cola de mensajes. El mínimo es de cuatro y el máximo de 32. Por defecto, 15.
Como implementar el campo flags.
flags = (mensajes_en_cola * 256) + (prioridad * 32) + bits_funcionamiento Por ejemplo, para un hilo asíncrono recursivo no protegido que notifique errores OLE, con prioridad normal y cola de 24 mensajes, flags = (24*256) + (2*32) + (16+4+2=22) [= 6262 (hex. 0x1876] Como puede comprobar el valor por defecto (3843 – hex. 0xF03), corresponde a 15 mensajes en espera (15*256), prioridad mínima (0*32), llamadas a eventos protegidas (1) y notificación de errores OLE (2).
StopThread
Detiene la ejecución del hilo indicado en nThreadID. Opcionalmente, puede indicar un método que se invocará justo antes de eliminar el hilo. Esta función no realiza devolución de valores de manera síncrona, y por defecto ignora los valores retornados por el método invocado. Puede cambiar este comportamiento configurando bWantReturn como verdadero, así forzará la devolución del valor de retorno; eso sí de manera asíncrona, siempre y cuando haya habilitado la recepción de eventos asíncronos.
Si no ha habido errores devolverá 0, en caso contrario un código de error.
Formatos (C & VB) long StopThread(long nThreadID, [BOOL bIgnoreMsgQueue = false], [BOOL bWantReturn = false], [short invKind = 1], [LPCTSTR Function], [VARIANT *params]);
StopThread(nThreadID as long, [bIgnoreMsgQueue as Boolean= false], [bWantReturn as Boolean = false], [invKind as integer = 1], [Function as string], [params as variant]) as long
nThreadID, ID del hilo a detener. Esta ID es la que devuelve CreateThread y que identifica a cada hilo.
bIgnoreMsgQueue, ignorar mensajes pendientes. Si está configurado como falso, se procesarán los mensajes pendientes antes de eliminar el hilo. Si está configurado como verdadero, se eliminará el hilo inmediatamente, ignorando mensajes pendientes.
bWantReturn, fuerza devolución de valor de retorno, (si no ha indicado Function, es ignorado).
invKind, modo de invocación del método indicado en Function, (vea notas).
Function, cadena con el nombre de la función a llamar antes de eliminar el hilo.
params, parámetro o parámetros a pasar al método invocado, (vea notas).
Códigos de error devueltos
0, ningún error. 1, se indicó bWantReturn pero no Function, no se devolverá valor de retorno. 4, Sin memoria. No se invocará Function, (vea apéndice A). 15 (hex. F), ID indicada no corresponde a ningún hilo activo.
Exec
Coloca un nuevo mensaje en la cola de mensajes del hilo indicado en nThreadID, este mensaje contiene la información recibida.
Si no ha habido errores devolverá 0, en caso contrario un código de error.
Formatos (C & VB) long Exec(long nThreadID, [BOOL bWantReturn = false], [short invKind = 1], [VARIANT *lpDispatch], [LPCTSTR Function], [VARIANT *params] [VARIANT *retval]);
Exec(nThreadID as long , [bWantReturn as boolean= false], [invKind as integer = 1], [lpDispatch as variant], [Function as string], [params as variant] [retval as variant]) as long
nThreadID, ID del hilo al que comunicar mensajes.
bWantReturn, fuerza devolución síncrona de valor de retorno.
invKind, modo de invocación del método indicado en Function, (vea notas).
lpDispatch, variant con una referencia válida a un objeto. Se utilizará en lugar del objeto predeterminado para invocar Function, (vea más adelante).
Function, cadena con el nombre de la función a llamar.
params, parámetro o parámetros a pasar al método invocado, (vea notas).
retval, variant donde colocar el resultado.
Códigos de error devueltos
0, ningún error. 1, No se indicó Function. No se realiza ninguna operación. 2, se ha sobrepasado el tiempo máximo de espera, (vea apéndice A). 4, Sin memoria. No se invocará Function, (idem). 8, mensaje rechazado, el máximo de mensajes pendientes ha sido alcanzado (vea nota). 15 (hex. F), ID indicada no corresponde a ningún hilo activo.
NOTA sobre uso del valor lpDispatch
En ocasiones el valor devuelto por el método X de un objeto Y, es una referencia a la interfaz de otro objeto Z. Puede usar esa referencia al llamar al método Exec, para indicarle que la función que desea invocar pertenece al objeto Z , y no al objeto predeterminado Y. Por ejemplo,
Dim miObjeto as new ThreadInterface Dim Database as variant Dim Recordset as variant Dim miThreadId as long Dim retval as long
Public Sub AlgunaSub() Set miObjeto = new ThreadInterface miThreadId = miObjeto.CreateThread('DAO.DBEngine.xx', -1 _ , , , 1, 'OpenDatabase', App.Path & 'datos\ejemplo.mdb' _ , Database, retval) miObjeto.Exec (miThreadID, True, 1, Database, 'OpenRecordset' _ , 'select * from titulos', Recordset, retval) End sub
Puede incluso, pasar esta referencia (en realidad un puntero al objeto) entre diferentes hilos, aunque respetando en este caso una norma básica: si el objeto pertenece al modelo apartamento de hilo único (vea apéndice C) debe ser invocado sólo por un hilo al mismo tiempo; si invoca el objeto desde el hilo que lo ha creado, pero al mismo tiempo pasa éste puntero a objeto a otro hilo, que a su vez invoca al objeto, puede afectar la integridad de los datos que éste maneja al intentar establecer al mismo tiempo el valor de una propiedad, por ejemplo.
Cada hilo, mantiene sincronizados datos propios, el objeto asociado, y los datos del objeto, no es posible una acción simultánea de lectura o escritura sobre los mismos. Pero si pasa punteros de un hilo a otro, deberá implementar algún tipo de medida que evite el acceso simultáneo a los datos de un objeto.
Recuerde, el objeto instanciado al crear el hilo, está garantizado contra el acceso simultáneo de varios hilos. Los objetos o interfaces a objetos creados por este objeto 'principal', están asímismo asegurados dentro del contexto en que fueron creados. Pero si pasa ese a objeto, a otro hilo, es su responsabilidad implementar algún tipo de sincronización.
Apéndices
Sobre los mensajes de error demasiado crípticos
Algunos mensajes de error a los que me he referido antes requieren una mejor explicación.
2, se ha sobrepasado el tiempo de espera
Uno de los parámetros que puede indicar al crear un hilo, es el intervalo máximo de espera de retorno. Esto es, el tiempo máximo (en milisegundos) que se esperará a que la función del objeto invocada retorne para así devolver el resultado. Si el procesamiento del método invocado del objeto OLE sobrepasa este máximo, CreateThread o Exec retornan sin esperar el valor de retorno del método del objeto, y devuelven este error. Esto, no implica que el método del objeto invocado, no haya retornado nada, o que haya sido anulado. Tan sólo, que no se espera más por él. Si no ha habilitado notificación asíncrona, el valor que devuelva el método del objeto al retornar, se habrá perdido. Puede, o bien establecer intervalos de espera largos (no se lo aconsejo), o bien habilitar notificación asíncrona. De todos modos, si espera que el método del objeto al que va a llamar superará este intervalo, o necesita ese resultado, puede forzar espera indefinida, configurando el parámetro bWantReturn como Verdadero (sólo para el método Exec). Si los métodos que un objeto expone retornan con rapidez, establezca un intervalo de espera corto, ya que si sólo unos pocos métodos del mismo necesitan intervalos más largos, puede forzar la devolución del valor de retorno o la notificación asíncrona.
4, Sin memoria
Este sería un error altamente excepcional. Dado que los hilos tienen una cola de entre 4 y 32 mensajes, los parámetros que se indicaron en su momento para cada mensaje se establecen y eliminan de manera dinámica. Este error significa que no hay memoria física disponible donde almacenar los datos.
Errores COM interceptables
Una vez disponga de un flamante objeto en un hilo y utilice la función Exec para invocar los métodos del mismo, puede darse el caso de que no exista el método al que haya invocado, o haya indicado parámetros erróneos, etc. Para facilitar el reconocimiento de este tipo de errores, dispone del evento GotCOMError, donde se notificarán los mismos. Su uso está pensado, para la fase de desarrollo de una librería o programa, donde la depuración de errores OLE no es tarea sencilla; en un ejecutable final deberá desabilitar esta funcionalidad.
Aquí tiene una lista de los posibles errores que puedan producirse (al menos la mayoría de ellos).
Código de error OLE | Descripción |
-2147024882 | Sin memoria :-( y sin comentarios ;-) |
-2147352570 | En nombre indicado no es conocido |
-2147352562 | El número de argumentos indicados no coincide con el número de argumentos acceptados por el método o propiedad |
-2147352567 | El objeto necesita levantar una excepción |
-2147352573 | El método solicitado no existe, o ha intentado modificar el valor de una propiedad de solo-lectura |
-2147352569 | Esta implementación no soporta argumentos con nombre. Se ha indicado invocación para establecer una propiedad, sobre un método |
-2147352566 | Uno de los argumentos no ha podido ser forzado al tipo especificado |
-2147352572 | Alguno de los parámetros no corresponde a un parárametro del método |
-2147352571 | Uno o más de los argumentos no ha podido ser forzado al tipo |
-2147352561 | Se ha omitido algún parámetro requerido |
|
Modelos multihilo
Al principio de este documento comentaba que libThr soporta tanto modelos de hilo en apartamento, como modelos multihilo libres.
Modelo de apartamento de hilo único (single-threaded apartment)
Los modelos de hilo en apartamento ofrecen una implementación basada en mensajes. Estos hilos viven en su apartamento. OLE crea una ventana oculta para cada apartamento. Cuando hace una llamada a un objeto del apartamento, es recibida como un mensaje de ventana por el procedimiento de esta ventana. Cuando el apartamento donde reside el objeto recibe y distribuye el mensaje, es esta ventana quien recibe el mensaje. El procedimiento de la ventana hace entonces la llamada al método correpondiente de la interfaz del objeto.
Así, cuando múltiples clientes llaman a un objeto, las llamadas se reciben en la cola de mensajes de la ventana y el objeto las recibe una a una. Como las llamadas son seriadas por OLE y distribuídas por el hilo que pertenece al apartamento del objeto no es necesario implementar sincronización.
Un grupo de objetos relacionados puede coexistir en un apartamento, y tienen ejecución sincronizada en su apartamento de hilo único. Sin embargo, un modelo basado en apartamento no puede residir más que en un único hilo.
Las llamadas entre los objetos de un mismo apartamento, no necesitan ser seriadas; pero si un objeto hace una llamada fuera de su apartamento esta llamada es empaquetada y seriada por OLE. Este es el mismo proceso que rige para hacer llamadas entre procesos o marshaling, que consiste en empaquetar y desempaquetar parámetros de manera que pueda tener lugar una llamada a procedimiento remoto (RPC).
Modelo multihilo libre (multi-threaded apartment)
Todos los hilos inicializados como libres Free, residen en un único apartamento, así que no es necesario el proceso de empaquetado (marshaling) entre ellos. Los hilos no necesitan procesar mensajes porque OLE no utiliza mensajes en este modelo.
Las llamadas a los objetos son posibles desde todos los hilos del apartamento. No hay seriación de llamadas, múltiples llamadas pueden llegar al mismo método u objeto al mismo tiempo. Los objetos de modelo libre deben ser capaces de manejar llamadas a sus métodos desde otros hilos en cualquier momento.
Este modelo ofrece el mejor rendimiento y aprovecha las ventajas del hardware multiprocesador para llamadas cruzadas entre hilos, procesos y máquinas, ya que las llamadas no son seriadas de manera alguna. Esto, sin embargo, supone que el objeto debe implementar su propia sincronización al exponer sus métodos.
Múltiples clientes pueden llamar al objeto que soporta el modelo libre de manera simultánea. En servidores fuera de proceso (exe), OLE crea un grupo de hilos en el servidor; una llamada (o múltiples llamadas) de un cliente puede ser repartida por cualquiera de esos hilos en cualquier momento. Los servidores in-proc (dll), pueden recibir directamente llamadas de múltiples hilos del cliente. Todos los hilos pertenecen al mismo apartamento, así, los punteros a interfaces, pueden pasar directamente de un hilo a otro sin empaquetado.
El hilo cliente queda suspendido mientras hace una llamada OLE a un objeto fuera de su apartamento y es resumido cuando la llamada vuelve. Las llamadas entre procesos son manejadas por RPC.
Consejos para obtener el mejor rendimiento
El mejor rendimiento lo obtendrá utilizando el modelo de apartamento libre, puesto que las llamadas OLE no son seriadas y múltiples hilos pueden acceder al mismo objeto al mismo tiempo.
Si planea utilizar varios hilos con notificaciones asíncronas de manera simultánea y no desea habilitar protección, no utilice variables globales en el cuerpo de las funciones de notificación asíncrona, a menos que sean de solo lectura. Observe el siguiente fragmento de código:
Private Sub SuObjeto_AsyncRetval(ByVal nThreadID As Long, ByVal retval As Variant)If IsMissing(retval) Or IsEmpty(retval) Or IsNull(retval) Then Exit Sub GlobalData = GlobalData + 1 If GlobalData = 5 Then... GlobalData = 0 ...End IfEnd Sub
Cada vez que la función es llamada se incrementa el valor de la variable GlobalData; si su valor es 5, se realizan una serie de operaciones y se le asigna el valor 0. Si un hilo llama a la función cuando la variable contiene el valor 3, incrementa esta variable y antes de chequear el contenido, el sistema pasa el control a otro hilo que a su vez modifica la misma, cuando el control retorne al primer hilo, el contenido de la variable no será el esperado 4 sino 5 (y se ejecutará el bloque if).
Este es el tipo de situación que debe tratar de evitar, bien empleando variables locales, con lo cual cada hilo trabajará con una copia distinta, bien usando arrays,
Private Sub SuObjeto_AsyncRetval(...)If IsMissing(retval) Or IsEmpty(retval) Or IsNull(retval) Then Exit Sub GlobalData(nThreadID) = GlobalData(nThreadID) + 1 If GlobalData(nThreadID) = 5 Then... GlobalData(nThreadID) = 0 ...End IfEnd Sub
Si le es absolutamente indispensable chequear una variable global, asegúrese que es de solo lectura o implemente algún tipo de sincronización. El caso más sencillo sería:
Private Sub SuObjeto_AsyncRetval(...)Static Lock as Boolean If IsMissing(retval) Or IsEmpty(retval) Or IsNull(retval) Then Exit Sub
Do while LockSleep(1) 'pasemos el control a otro hiloLoop
Lock = true GlobalData = GlobalData + 1 If GlobalData = 5 Then... GlobalData = 0 ...End If Lock = falseEnd Sub
Si aún así, experimenta problemas, deberá habilitar protección.
Registrarse
No voy a repetirle lo que es shareware, si lo desea puede leer la nota al final del texto. Pero si algunos apuntes de mi manera de ver las cosas.
En primer lugar, al diseñar esta librería he pensado en facilitar el uso de características interesantes de los ‘modernos’ sistemas operativos, que de otro modo sólo son accesibles por medio de continuas llamadas al API de Windooze. Considero que lo encontrará práctico y fácil de usar.
Me ha llevado un tiempo y me he tomado un trabajo hasta que he quedado satisfecho del funcionamiento de un componente del que espero sirva de ayuda en sus desarrollos.
Por otro lado, trato de ganarme la vida con esto, con lo cual debo buscar una manera de compersar el trabajo que me tomo, y las horas de ‘autismo’ que soportan quienes viven conmigo.
No soy partidario de facilitar componentes con características mermadas o limitadas en el tiempo, así que libThr es totalmente funcional. Pero considero que quienes se registran deben disfrutar de alguna ventaja, por pequeña que esta sea, frente a quienes no lo hacen. En este caso, la única ventaja de registrarse (aparte de contribuir a mantener la filosofía shareware) será eliminar la ventana que aparece cuando la librería se carga en memoria.
Para registrarse notifíquelo en la direccion kikusi@arrakis.es, indicando los siguientes datos de registro:
Nombre (ya sea particular o comercial). Dirección de correo (eMail). Número de licencias (solo licencias empresariales). No olvide indicar la librería que desea registrar (libThr).
La forma de pago será (preferiblemente) por transferencia bancaria a la cuenta:
2038-4028-57-6000036475
o bien por giro postal o cheque pagaderos a:
Miguel Perancho Hevia San Bartolomeo da Freixa 32514 - Boborás, Ourense España
indicando en cualquier caso como concepto el nombre usado para registrarse. Una vez hecho efectivo, recibirá el número de licencia para registrar el componente en la dirección solicitada.
Lista de precios: (Ene-Dic 2.000)
libThr, desarrolladores particulares,
Licencia monopuesto, 9.875 pts, 62 euro, $62 US
libThr, empresas,
Licencia única, 60.000 pts, 375 euro, $375 US
Licencia total, a consultar
libThr, centros de enseñanza,
Consultar para condiciones especiales
Recuerde, libThr es una librería, no un programa. Es el desarrollador quien satisface la cuota de registro, no el usuario final. Una vez registrado, el desarrollador obtiene una licencia MONOPUESTO completa. Puede utilizarla en sus proyectos y distribuírla con ellos sin cargo alguno. Obviamente, un desarrollador/un puesto, una licencia.
Más información - Descargas.
Acerca del shareware
Hasta hace bien poco, los programas comerciales se compraban prácticamente a ciegas, siendo después de la compra cuando evaluábamos el producto; a no ser que, ante la perspectiva de un volumen grande de ventas, la empresa responsable del desarrollo o comercialización de la misma, se aviniese a hacernos una demostración. Hoy en día es más habitual que, de manera anterior al lanzamiento de la versión final de un programa, pase por nuestras manos una 'pre-Release', 'Demo' o 'Beta' del mismo con características recortadas o limitada en el tiempo con el fin de evaluar su funcionamiento y/o rendimiento. El auge del shareware tiene bastante que ver con esto.
Ya ha pasado el tiempo en que el shareware y el freeware era software de menor calidad que los programas comerciales; como muestra puede probar cualquiera de los programas share de diseño o retoque que seguramente pueda instalar desde el cd-rom que le regala su revista favorita.
Como bien sabe la filosofía sobre la que se apoya el shareware es la de ‘probar antes de comprar’. Así, frente al programa comercial, un usuario comprueba las excelencias o carencias del software antes de tomar una decisión sobre la adquisición del mismo. Y en este caso, el desembolso exigido es menor. También es cierto (aunque no siempre) que un programador, o pequeño grupo de programadores, no puede competir frente a un equipo formado por numerosas personas y presupuestos millonarios a la hora de diseñar proyectos complejos. En cambio, sí puede ofrecer otro tipo de soluciones (ya sean componentes, add-ins o programas completos) de utilidad para el resto de usuarios.
Las personas que pasamos largos ratos delante de la dichosa maquinita, en ocasiones nos topamos con que al fabuloso programa ,que ocupa 200 ricos megas de nuestro disco duro y que tanto y tan bien hace, le falta algun detalle que nos facilitaría el trabajo en gran medida, detalle al que algún programador, en alguna parte del mundo, le ha encontrado solución y nos la ofrece a cambio de una compensación a su dedicación, generalmente poco onerosa.
El shareware se basa en la confianza que deposita el programador de una pequeña utilidad que nos ahorra algún esfuerzo en que nosotros compensemos el suyo. No es raro encontrar aquellos que sólo piden que les enviemos una tarjeta informando de quienes somos, dónde nos encontramos y que nos parece su trabajo, aunque es más habitual, y yo en este caso así lo he decidido, que la compensación sea económica.
Esto es shareware. Yo ofrezco algo que cualquiera puede tomar y probar cuanto guste. Si ese alguien, después de probarlo, decide usarlo en la práctica, es libre de hacerlo siempre y cuando cumpla las condiciones en que se lo ofrezco.
Todo esto no sirve de nada sin el apoyo de los usuarios. Si nadie se registrase, el programador dejaría de desarrollar para el usuario en general, y quedaríamos en manos de los de siempre. Las ventajas de apoyar el shareware son obvias: disponer de una ingente cantidad de software frente al (generalmente más caro, aunque generalmente también más completo) software 'comercial'.
Pero al menos tendrá la oportunidad de elegir entre muchas posibilidades y no verse forzado a escoger entre unos pocos.
Ahora, usted es quien decide...
El autor (o culpable)
Más información - Descargas.
libThr v2.70.058, Copyright © El Zooilógico, 2.000
| |