Ejemplos
Tres programas completos, tal como se ejecutan. Están adaptados de los ejemplos que viven en el paquete de FoxCore.Runtime 3.4.2, en la carpeta ejemplos\: se pueden copiar, pegar en un .prg y ejecutar desde el IDE de Visual FoxPro.
.prg del paquete leen el JWT de un fichero del disco porque son ficheros sueltos que ejecutas a mano y tienen que sacarlo de algún sitio; aquí va en una constante, que es lo que hace una aplicación de verdad. Ver Licencias.1. Una tarea larga que avisa por dónde va
El worker llama a runtime.Progress(), y aquí se recoge de las dos formas: preguntando con GetProgress() y siendo avisado por un sink. La que importa entender es la segunda.
LOCAL loRt, loEco, lcId, lcScript, lcJwt
lcJwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...." && pega aquí el JWT de tu portal de cliente
loRt = CREATEOBJECT("FoxCore.Runtime")
IF !loRt.Activate(lcJwt)
MESSAGEBOX("La licencia de FoxCore no es valida.", 16, "FoxCore")
RETURN
ENDIF
loRt.Attach(_VFP)
loEco = CREATEOBJECT("EcoDeProgreso")
? "sink aceptado :", loRt.SetEventSink(loEco)
TEXT TO lcScript NOSHOW
FOR i = 1 TO 5
runtime.Progress(i * 20, 'paso ' + TRANSFORM(i) + ' de 5')
=INKEY(0.2)
ENDFOR
runtime.Log('info', 'terminado sin novedad')
RETURN 'listo'
ENDTEXT
lcId = loRt.RunScript(lcScript, "con progreso")
DO WHILE !loRt.IsCompleted(lcId)
loRt.Pump() && el sink se llama DESDE AQUÍ
ENDDO
loRt.Pump(200, 1000) && una última vuelta: pueden quedar avisos en cola
? "avisos recibidos :", loEco.nAvisos
? "ultimo aviso :", loEco.cUltimo
? "ultimo log :", loEco.cLog
? "y sin sink :", loRt.GetProgress(lcId), loRt.GetProgressMessage(lcId)
loRt.ClearEventSink() && importante: mientras lo tenga, tu objeto vive
loRt.Shutdown()
RETURN
DEFINE CLASS EcoDeProgreso AS Custom
nAvisos = 0
cUltimo = ""
cLog = ""
*-- OnProgress y OnLog son OBLIGATORIOS: sin los dos, SetEventSink da .F.
PROCEDURE OnProgress(tcTaskId, tnPercent, tcMessage)
THIS.nAvisos = THIS.nAvisos + 1
THIS.cUltimo = TRANSFORM(tnPercent) + "% " + TRANSFORM(tcMessage)
ENDPROC
PROCEDURE OnLog(tcTaskId, tcLevel, tcMessage)
THIS.cLog = TRANSFORM(tcLevel) + ": " + TRANSFORM(tcMessage)
ENDPROC
*-- Este es opcional: se detecta si esta, y si no, no pasa nada.
PROCEDURE OnTaskCompleted(tcTaskId, tcState)
ENDPROC
ENDDEFINEQué verás
sink aceptado : .T.
avisos recibidos : 5
ultimo aviso : 100% paso 5 de 5
ultimo log : info: terminado sin novedad
y sin sink : 100,00 paso 5 de 52. Repartir una tabla entre varios workers
La receta más pedida, y la que más se hace mal. La clave es que NO se reparte el cursor: se reparte el RANGO. Cada worker abre la tabla por su cuenta y se ocupa de un tramo, así que no hay nada compartido y no hace falta bloquear.
LOCAL loRt, lcDbf, i, lnDesde, lnHasta, lnPorTramo, lnTotal, lnHechas, lcTerm, lcJwt
LOCAL ARRAY laIds[4], laTerm[1]
lcJwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...." && pega aquí el JWT de tu portal de cliente
*-- Una tabla de ejemplo con 400 lineas
CREATE CURSOR ventas (id I, importe Y)
FOR i = 1 TO 400
INSERT INTO ventas VALUES (i, i * 1.25)
ENDFOR
lcDbf = ADDBS(SYS(2023)) + "r09_ventas.dbf"
COPY TO (lcDbf)
USE IN SELECT("ventas")
loRt = CREATEOBJECT("FoxCore.Runtime")
IF !loRt.Activate(lcJwt)
MESSAGEBOX("La licencia de FoxCore no es valida.", 16, "FoxCore")
RETURN
ENDIF
loRt.Attach(_VFP)
loRt.MaxWorkers = 4
*-- Cuatro tramos de 100. cada worker abre la tabla POR SU CUENTA y suma el suyo.
lnPorTramo = 100
FOR i = 1 TO 4
lnDesde = (i - 1) * lnPorTramo + 1
lnHasta = i * lnPorTramo
laIds[i] = loRt.RunScript( ;
"USE '" + lcDbf + "' IN 0 SHARED ALIAS ventas" + CHR(13) + CHR(10) + ;
"SELECT ventas" + CHR(13) + CHR(10) + ;
"SUM importe TO lnT FOR BETWEEN(id, " + TRANSFORM(lnDesde) + ", " + TRANSFORM(lnHasta) + ")" + CHR(13) + CHR(10) + ;
"USE IN ventas" + CHR(13) + CHR(10) + ;
"RETURN lnT", "tramo " + TRANSFORM(i))
ENDFOR
lnHechas = 0
DO WHILE lnHechas < 4
loRt.Pump()
lcTerm = loRt.PollCompleted()
IF !EMPTY(lcTerm)
lnHechas = lnHechas + ALINES(laTerm, lcTerm, 1, ";")
ENDIF
ENDDO
*-- y se junta aquí, donde hay un solo hilo
lnTotal = 0
FOR i = 1 TO 4
? " tramo " + TRANSFORM(i) + " :", loRt.GetState(laIds[i]), loRt.GetResult(laIds[i])
IF loRt.GetState(laIds[i]) == "Completed"
lnTotal = lnTotal + loRt.GetResult(laIds[i])
ENDIF
ENDFOR
? "total en paralelo :", lnTotal, " (esperado 100250,00)"
ERASE (lcDbf)
loRt.Shutdown()
RETURNQué verás
tramo 1 : Completed 6312,50
tramo 2 : Completed 18812,50
tramo 3 : Completed 31312,50
tramo 4 : Completed 43812,50
total en paralelo : 100250,00 (esperado 100250,00)3. Un handler propio, llamado desde el worker
El caso clásico: un correlativo. Ocho procesos pidiendo número a la vez y ni uno repetido, con un objeto que no tiene un solo bloqueo.
LOCAL loRt, loCaja, i, lnHechas, lcTerminadas, lcJwt
LOCAL ARRAY laIds[8], laTerm[1]
lcJwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...." && pega aquí el JWT de tu portal de cliente
loRt = CREATEOBJECT("FoxCore.Runtime")
IF !loRt.Activate(lcJwt)
MESSAGEBOX("La licencia de FoxCore no es valida.", 16, "FoxCore")
RETURN
ENDIF
loRt.Attach(_VFP)
loRt.MaxWorkers = 4
loCaja = CREATEOBJECT("CajaDeFolios")
? "handler aceptado :", loRt.RegisterHandler("SiguienteFolio", loCaja, "Siguiente")
FOR i = 1 TO 8
laIds[i] = loRt.RunScript("RETURN runtime.Call('SiguienteFolio', 'FRA')", "folio " + TRANSFORM(i))
ENDFOR
lnHechas = 0
DO WHILE lnHechas < 8
loRt.Pump() && aquí se atienden las llamadas
lcTerminadas = loRt.PollCompleted()
IF !EMPTY(lcTerminadas)
lnHechas = lnHechas + ALINES(laTerm, lcTerminadas, 1, ";")
ENDIF
ENDDO
? "folios emitidos :", loCaja.nUltimo
FOR i = 1 TO 8
?? " " + TRANSFORM(loRt.GetResult(laIds[i]))
ENDFOR
loRt.Shutdown()
RETURN
DEFINE CLASS CajaDeFolios AS Custom
nUltimo = 0
*-- sin un solo bloqueo, y no hace falta: esto se ejecuta en el hilo del host,
*-- de una llamada en una llamada.
FUNCTION Siguiente(tcSerie)
THIS.nUltimo = THIS.nUltimo + 1
RETURN TRANSFORM(tcSerie) + "-" + PADL(THIS.nUltimo, 4, "0")
ENDFUNC
ENDDEFINEQué verás
handler aceptado : .T.
folios emitidos : 8 FRA-0001 FRA-0002 FRA-0003 FRA-0004 FRA-0005 FRA-0006 FRA-0007 FRA-0008FoxCoreClass, Run() ni WaitFor(). La tabla de equivalencias está en «Lo que ya no existe», al final de la Referencia de API. Siguiente: Referencia API ->