Multihilos
#1
Posted 07 January 2009 - 03:41 PM
Yo he visto mucha información sobre multi hilos, sin embargo, sigo sin entender exactamente como funcionan, quiero decir, veo como se crean hilos y como se deben ejecutar, sin embargo aun no entiendo como hacer que un programa ya hecho, cambiarlo a ese concepto.
Les explico.
Tengo un producto donde se generan varias acciones como son Registro de Clientes, Registro de Ventas, Registro de Pagos, Facturación, pero por otro lado este programa está en constante comunicación con dos puertos seriales, donde genera eventos de entrada y salida.
Que pasa, mientras estoy realizando alguna actividad de registro o de facturación, los puertos seriales deben estar trabajando sin interrupción, la parte de salida del programa al puerto no tiene problema ya que se ejecuta dependiendo de el proceso que se haga, sin embargo, el problema surge cuando se recibe información del puerto.
Como hago para separar en hilos estos procesos, debo tener hilos solo para los puertos y las demas actividades no requieren estar dentro de un hilo?
O todos deben de tener su hilo correspondiente.
Espero que hay sido claro.....
Salud OS
#2
Posted 07 January 2009 - 03:51 PM
#3
Posted 07 January 2009 - 04:01 PM
Creo que es una estupenda idea que metas el manejo de puertos en un hilo diferente al principal. Y no necesitas meter cada cosa en un hilo, basta con que crees un hilo (o dos, uno por cada puerto) y este se ejecutara de forma paralela al hilo principal. Yo incluso iría un poco mas allá y no utilizaría componentes para manejar el puerto serie, con sus eventos y demás, usando hilos es mucho mas adecuado y sencillo acceder al puerto serie usando directamente las funciones de la API.
, amigo, eso se lee muy fácil, solo que no se como leer puertos con API , me puedes enseñar
Salud OS
Edito: Me senti como cierto individuo que se hace llamar NOVATO jejejeje,
#4
Posted 07 January 2009 - 04:08 PM
, amigo, eso se lee muy fácil, solo que no se como leer puertos con API , me puedes enseñar
Eso ni se pregunta
Básicamente se debe de abrir el puerto como si de un archivo se tratase (usando la funciona CreateFile), configurar los parámetros de comunicación (velocidad, paridad, control de flujo, etc ...) y no queda mas que leer y escribir en el puerto usando las mismas funciones que utilizarías para leer y escribir un archivo (ReadFile, WriteFile). Si me describes un poco el protocolo de comunicación que utilizas puedo hacerte un ejemplo un poco mas concreto.
#5
Posted 07 January 2009 - 04:21 PM
Eso ni se pregunta
Básicamente se debe de abrir el puerto como si de un archivo se tratase (usando la funciona CreateFile), configurar los parámetros de comunicación (velocidad, paridad, control de flujo, etc ...) y no queda mas que leer y escribir en el puerto usando las mismas funciones que utilizarías para leer y escribir un archivo (ReadFile, WriteFile). Si me describes un poco el protocolo de comunicación que utilizas puedo hacerte un ejemplo un poco mas concreto.
Perfecto amigo,
El protocolo es diferente dependiendo del equipo al cual se conecta, esto quiere decir que se debe hacer una mascara por cada equipo.
Los más comunes son los siguientes:
Formato 1
STX = #2
ETX = #3
EOT = #4
ACK = #6
NAK = #21
BCC = Binary Code Check (XOR longitudinal)
<STX> [Cadena] <ETX> <BCC>
Al recibir la cadena incluyendo el BCC (que es diferente dependiendo de la cadena enviada) se debe de enviar ACK si es correcto o NAK si es incorrecto.
Este protocolo tiene la característica de que se debe enviar un HeartBit cada x segundos para verificar la sincronía con el equipo.
Además, el equipo es mandatorio, es decir, si tiene algo que enviar se debe esperar a que libere su buffer para poder enviarle datos.
Formato 2
[Cadena]<LF><CR>
Este formato es muy simple y es unidireccional, solo debo recibir datos y no requiere de contestación.
Si requieres mas información con gusto te la daré.
Salud OS
#6
Posted 07 January 2009 - 04:25 PM
#7
Posted 07 January 2009 - 04:29 PM
Un par de preguntas: Para que el equipo envíe datos hay que mandarle algún comando o lo hace cuando el quiere, y si es así, y manda datos cuando el ordenador esta apagado ¿los datos se pierden?
Bueno, en el formato 1 es una comunicación en tiempo real, si no estan "en linea" los equipos esa información se pierde, en el formato 2 algunos equipos tienen un buffer interno que almacena cierta cantidad de datos, pero una vez que se llena ese buffer va eliminando los mas viejos e ingresa los mas nuevos.
Referente a la pregunta de comandos, cuando yo rquiero enviar algo al equipo necesito enviarle una cadena para que el equipo me diga si está listo para recibir o tiene algo que enviarme, si tiene algo que enviarme, debo esperar a que lo envie y despues enviar lo que quiero.
Salud OS
#8
Posted 07 January 2009 - 04:31 PM
¿De que dispositivos se trata?
¿Usaran las señales de control del puerto serie para saber si tienen que enviar? Es que me parece muy peligroso que envien los datos alegremente
#9
Posted 07 January 2009 - 04:33 PM
Perdona si estoy muy pregunton
¿De que dispositivos se trata?
¿Usaran las señales de control del puerto serie para saber si tienen que enviar? Es que me parece muy peligroso que envien los datos alegremente
Son conmutadores telefonicos, centrales telefonicas, PBX no se como lo conozcas.
No se usan señales de control, solo son datos (caracteres) a traves del puerto.
Salud OS
#10
Posted 07 January 2009 - 04:42 PM
El primer caso ya tiene mas complejidad, pero no parece complicado.
Dejame un poco de tiempo y a lo mejor te puedo mostrar algo de código
#11
Posted 07 January 2009 - 04:44 PM
Pues tal como lo veo, el segundo caso es muy simple, hay que leer continuamente el puerto serie guardando el texto en un buffer (un simple string) y cuando se recibe el LF procesar la cadena (no se si la guardas en una base de datos, un fichero, ...).
El primer caso ya tiene mas complejidad, pero no parece complicado.
Dejame un poco de tiempo y a lo mejor te puedo mostrar algo de código
, gracias amigo, esto va a ser muy util para mi y para muchos mas
Salud OS
#12
Posted 08 January 2009 - 10:27 AM
Eliseo sin usar componentes actualmente estoy trabajando en este codigo, aun hay problemas pero funciona:
unit UComm; interface uses Forms, SysUtils, Classes, IniFiles, Windows, StrUtils, Dialogs, ComCFG; //esta es una unidad con un formulario const Archivo = 'ComPP.ini'; STX = #2; CR = #13; LF = #10; var nombrePuerto,paridadPuerto,flujoPuerto: string; formato, baudPuerto, bitsPuerto, stopPuerto, HandlePuerto: integer; Arranque, longCadena, Terminador: byte; function ShowComCFG: integer; stdcall; function CapturaCadena: PChar; stdcall; procedure CargaAjustes; stdcall; procedure AbrePuerto; stdcall; procedure CierraPuerto; stdcall; implementation function ShowComCFG:integer;stdcall; begin fmComm := TfmComm.Create(nil); Result := fmComm.ShowModal; end; procedure CargaAjustes; stdcall; var F: TIniFile; begin F := TIniFile.Create(ExtractFilePath(ParamStr(0)) + Archivo); with F do try nombrePuerto := ReadString('Puerto Serie','Puerto','COM1'); baudPuerto := ReadInteger('Puerto Serie','Baudios',9600); bitsPuerto := ReadInteger('Puerto Serie','Bits',8); paridadPuerto := ReadString('Puerto Serie','Paridad','Ninguna'); stopPuerto := ReadInteger('Puerto Serie','Bits Parada',1); //flujoPuerto := ReadString('Puerto Serie','Control de flujo','Ninguno'); formato := ReadInteger('Puerto Serie','Formatos',0); finally Free ; end; case formato of 0 : begin Arranque := Ord(STX); Terminador := Ord(CR); longCadena := 13; end; 1 : begin Arranque := Ord(STX); Terminador := Ord(LF); longCadena := 10; end; end; end; procedure AbrePuerto; stdcall; var Puerto: integer; Mode: DCB; //estructura de windows.dcb para control de dispositivos serie begin Mode.BaudRate := baudPuerto; Mode.ByteSize := bitspuerto; if paridadPuerto = 'Impar' then Mode.Parity := ODDPARITY else if paridadPuerto = 'Par' then Mode.Parity := EVENPARITY else if paridadPuerto = 'Marca' then Mode.Parity := MARKPARITY else if ParidadPuerto = 'Espacio' then Mode.Parity := SPACEPARITY else Mode.Parity := NOPARITY; case stopPuerto of 1 : Mode.StopBits := ONESTOPBIT; 2 : mode.StopBits := ONE5STOPBITS; 3 : Mode.StopBits := TWOSTOPBITS; else Mode.StopBits := ONESTOPBIT; end; HandlePuerto:=FileOpen(nombrePuerto,fmOpenRead);//Abre el puerto if HandlePuerto > 0 then SetCommState(HandlePuerto, Mode) //Ajusta los parámetros else ShowMessage('El puerto de comunicaciones ' + nombrePuerto + ' esta ocupado o no existe'); end; function CapturaCadena: PChar; stdcall; var n: integer; num: byte; Cad: string; begin result := ''; repeat Application.ProcessMessages ; FileRead(HandlePuerto,Num,1); //AQUI se genera un error. Si no captura nada (el equipo esta desconectado) se bloquea la ejecucion if (num = Arranque) then cad := '' else begin if num >= 32 then cad := cad + chr(num); end; if (GetKeyState(VK_ESCAPE) and 128 = 128) then begin Cad := '0'; Break; end; until (num = Terminador); result := PChar(Cad); end; procedure CierraPuerto; stdcall; begin FileClose(HandlePuerto); //Cierra el Puerto end; end.
La parte de captura cadena es codigo tuyo de otro hilo que postee por aqui. Todo esto en si esta en un proyecto de dll. igual se puede mejorar.
#13
Posted 08 January 2009 - 11:04 AM
Saludos.
#14
Posted 08 January 2009 - 11:04 AM
Vamos a darle un vistazo, seguro va a salir algo muy bueno de aqui
Salud OS
#15
Posted 08 January 2009 - 11:45 PM
Una pequeñá descripcion de los componentes:
-------------------------------------------------------------------
TtdThreadWrapper
A component that allows you to just drop the component on the form, implement its OnExecute event handler,
and call the Start method to start the thread.
TtdFuture
A sort of thread-based function. You can start the thread normally, and when you call the Value method
to get the "function value," it will wait for the thread to finish, so you can start the thread in the background
and call the Value method when you need to have the value before continuing.
TtdThreadPool
A component for implementing a simple thread pool. You attach a "work....
--------------------------------------------------------------------------------
La descripcion completa aqui: http://www.twodesks....ts/threads.html
Y el link de descargar aqui: http://www.twodesks....onentsSetup.zip
Son componentes gratis.
Salu2.
#16
Posted 09 January 2009 - 10:40 AM
#17
Posted 09 January 2009 - 10:47 AM
Hola, ha decir verdad yo no se nada de multihilos mas que solo teoria, nunca los he puesto en practica, pero he investigado mucho anteriormente y llegue a descargarme unos componentes para manejar los hilos que pintan muy bien.
Muchas gracias MasterXP les voy a dar un vistazo, debo comentar que he replicado algunas de las soluciones que he encontrado en internet pero mi problema realmente es que no logro entender el concepto, quiero decir, he creado multihilos pero sigo sin poder hacerlo en mi desarrollo, no logro conceptualizar como de una applicación ya hecha, hacerla multihilos.
Salud OS
#18
Posted 09 January 2009 - 02:37 PM
Aquí tienes un ejemplo de como leer el puerto serie en un thread. He usado el formato 2, mas que nada porque es mas fácil de implementar , pero el formato 1 no sería mucho mas complicado. Por ahora solo lee las lineas y las guarda en un fichero de texto, pero podemos hacer con ellas lo que queramos, solamente dime que hay que hacer con ellas
unit UThreadCom; interface uses Windows, Sysutils, Classes; type EThreadCom = class(Exception); TThreadCom = class(TThread) private FHandle: THandle; FPort: Integer; procedure Procesar(Str: String); protected procedure Execute; override; public constructor Create(APort: Integer); destructor Destroy; override; end; implementation // Esta funcion solo esta para hacer pruebas procedure log(Mensaje: String); var F: TextFile; Filename: String; Mutex: THandle; SearchRec: TSearchRec; begin // Insertamos la fecha y la hora Mensaje:= FormatDateTime('[ddd dd mmm, hh:nn] ', Now) + Mensaje; // El nombre del archivo es igual al del ejecutable, pero con la extension .log Filename:= ChangeFileExt(ParamStr(0),'.log'); // Creamos un mutex, usando como identificador unico la ruta completa del ejecutable Mutex:= CreateMutex(nil,FALSE, PChar(StringReplace(ParamStr(0),'\','/',[rfReplaceAll]))); if Mutex <> 0 then begin // Esperamos nuestro turno para escribir WaitForSingleObject(Mutex, INFINITE); try // Comprobamos el tamaño del archivo if FindFirst(Filename,faAnyFile,SearchRec) = 0 then begin // Si es mayor de un mega lo copiamos a (nombre).log.1 if SearchRec.Size > (1024*1024) then MoveFileEx(PChar(Filename),PChar(Filename + '.1'), MOVEFILE_REPLACE_EXISTING); FindClose(SearchRec); end; try AssignFile(F, Filename); {$I-} Append(F); if IOResult <> 0 then Rewrite(F); {$I+} if IOResult = 0 then begin // Escribimos el mensaje Writeln(F,Mensaje); CloseFile(F); end; except // end; finally ReleaseMutex(Mutex); CloseHandle(Mutex); end; end; end; { TThreadCom } constructor TThreadCom.Create(APort: Integer); var DCB: TDCB; begin FPort:= APort; FHandle:= CreateFile(PChar('\\.\COM' + IntToStr(APort)), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if FHandle = INVALID_HANDLE_VALUE then raise EThreadCom.Create('No puedo abrir el puerto COM' + IntToStr(APort)); DCB.DCBlength:= Sizeof(DCB); if not GetCommState(FHandle,DCB) then raise EThreadCom.Create('Error al configurar el puerto'); // Aqui esta la configuracion del puerto. Se la podriamos pasar por parametros ... with DCB do begin BaudRate := CBR_9600; ByteSize := 8; Parity := NOPARITY; StopBits := ONESTOPBIT; Flags := $01; end; if not SetCommState(FHandle, DCB) then raise EThreadCom.Create('Error al configurar el puerto'); inherited Create(FALSE); end; destructor TThreadCom.Destroy; begin if FHandle = INVALID_HANDLE_VALUE then CloseHandle(FHandle); inherited; end; const CR = #13; LF = #10; BUFFERSIZE = 1024; procedure TThreadCom.Execute; var Err: DWORD; COMSTAT: TCOMSTAT; Buffer: PChar; Str: String; i: Integer; begin Getmem(Buffer,BUFFERSIZE); try Str:= EmptyStr; while not Terminated do if ClearCommError(FHandle,Err,@COMSTAT) then if COMSTAT.cbInQue > 0 then begin if COMSTAT.cbInQue >= BUFFERSIZE then Err:= BUFFERSIZE - 1 else Err:= COMSTAT.cbInQue; FillChar(Buffer^,BUFFERSIZE,#0); if ReadFile(FHandle,Buffer^,Err,Err,nil) then begin Str:= Str + String(Buffer); i:= Pos(CR,Str); while i > 0 do begin Procesar(Trim(Copy(Str,1,i-1))); Delete(Str,1,i); i:= Pos(CR,Str); end; end else begin // Aqui podemos guardar un error en el log end; end else Sleep(10); finally FreeMem(Buffer); end; end; procedure TThreadCom.Procesar(Str: String); begin // Aqui procesamos cada una de las lineas de texto // Para hacer pruebas log(Str); end; end.
Para usar la unit anterior solamente tienes que añadirla a las uses y crear el thread:
MiThread:= TThreadCom.Create(2);
Y acuerdate de "terminarlo" cuando no lo necesites:
MiThread.FreeOnTerminate:= TRUE; MiThread.Terminate;
Para probarlo he usado un puerto com virtual, te lo recomiendo si no tienes ningún hardware a mano con que probarlo:
http://www.eterlogic...ducts.VSPE.html
:^) Creo que no se me olvida nada ...
#19
Posted 09 January 2009 - 02:39 PM
Salud OS
#20
Posted 09 January 2009 - 03:09 PM
Ya he probado el codigo, este es el resultado de los eventos
[Vie 09 Ene, 15:04] 2958 100 001 00:00:27 07/01/09 16:58 O7320170 ** 0 0 [Vie 09 Ene, 15:04] 2959 100 001 00:00:58 07/01/09 17:00 O7321280 ** 0 0 [Vie 09 Ene, 15:04] 2960 100 001 00:00:09 07/01/09 17:01 O7321055 ** 0 0 [Vie 09 Ene, 15:05] [Vie 09 Ene, 15:05] // 102 Clean 09/06/07-21:50 [Vie 09 Ene, 15:05] [Vie 09 Ene, 15:05] // 102 Dirty 09/06/07-21:51
Simplemente genial,
Salud OS