Hola,
les cuento que estoy haciendo una aplicación en ASP.NET MVC usando NHIbernate y Ninject (clásica aplicación con ABMS y procesos, nada espectacular).
Me llego el momento de hacer un proceso que barra la base y empiece a hacer cambios de estado, mandar mails, etc… En fin, el batch que usualmente tenes en las aplicaciones de negocio que debería correrse todas las noches.
La pregunta que tengo es, ¿Cómo implementan estos procesos?
Yo alguna vez hice uno en ASP.NET (sin MVC) y la forma más limpia que encontré como para no tener que preocuparme por los timeouts del servidor, etc. Fue
- Una variable estática para saber que el proceso estaba corriendo (con pocos usuarios mucho problema no me daba, lo lógico hubiera sido meter algo en la DB para que funcionara correctamente pero alcanzo).
- Lanzaba un thread con el proceso
- Dentro del thread, ya por cuestiones de manejo de sesión hacia “paquetes” de datos a procesar y los ejecutaba, de esta forma mantenía un poco de control respecto a los volúmenes de datos que manejaba cada sesión de NH y aparte podía mandar los paquetes al ThreadPool y hacer mi proceso multithreading sin meterme en cosas raras (cada paquete a procesar tenía su sesión y corria en su thread, por lo tanto muerto el paquete, muerta la sesión de NH y sus datos).
- Para que quede más bonito, si el proceso lo lanzaba un usuario, mediante un GUID que identificaba la ejecución del proceso, la aplicación cliente podía preguntar por el % de avance y actualizar un gauge.
La realidad es que esto lo arme así más que nada por gusto a complicarme, no era tanto el volumen de datos, al punto que el timeout del request no iba a ser un problema, pero es más fuerte que yo saber que hago las cosas “bien” (de esa forma me siento menos mal cuando a último momento termino poniendo algún clavito xD).
En fin, ¿alguien tiene otras recomendaciones acerca de cómo ejecutar procesos “pesados” en el servidor? ¿Llaman directamente al controller y que ejecute y no se preocupan por el timeout? ¿Tiran todo al threadpool y confían en que el proceso termine sin problema?
Aclaro: Esta aplicación se instalara en un hosting de los baratos… así que meter un servicio o tirar un exe es imposible, casi todos los schedules de procesos que vi en los hostings son “URLS” que invocan en determinados horarios, por lo que el proceso tiene que lanzarse desde una invocación a un action de un controller.
Saludos y gracias
Leandro
--
Has recibido este mensaje porque estás suscrito al grupo "AltNet-Hispano" de Grupos de Google.
Para publicar una entrada en este grupo, envía un correo electrónico a altnet-...@googlegroups.com.
Para anular tu suscripción a este grupo, envía un correo electrónico a altnet-hispan...@googlegroups.com
Para tener acceso a más opciones, visita el grupo en http://groups.google.com/group/altnet-hispano?hl=es.
Jorge, me doy idea de cómo implementar algunos puntos, pero me queda una duda con el más básico:
¿La idea de usar WCF es por separar conceptos o porque WCF te da algún beneficio en estos escenarios?
Gracias.
No,
la verdad que en esta aplicación tranquilamente puedo hacer correr el proceso nocturno en un request, en el peor de los casos partirlo en 2 o 3 procesos que corran en diferentes request.
La duda que tenía principalmente es saber qué alternativas hay para programar procesos “pesados” en web. En la solución que arme en su momento, el thread lo implemente por una cuestión simple, necesitaba liberar el request y devolver algo que identifique la corrida para posteriores consultas por el estado.
Vos propones anidar llamados a request del propio sitio partiendo el proceso. Es una idea interesante…
Saludos y gracias
From: altnet-...@googlegroups.com [mailto:altnet-...@googlegroups.com] On Behalf Of Nicolas Garfinkiel
Sent: Sunday, April 17, 2011 7:20 PM
To: altnet-...@googlegroups.com
Subject: Re: [altnet-hispano] ASP.NET MVC | Procesos "intensivos en el servidor"
se me ocurren dos enfoques posibles a partir de lo ya expresado en este hilo.
1. Un proceso robusto, tolerante a caidas, que marque registros a
procesar, procesados, etc... para esto me inclino mas por el servicio
de windows aunque podés lograr el mismo efecto agregando una líneas en
el Application_Start del Global.asax para chequear si hay procesos
pendiente.
2. Un proceso mas simple donde, por ejemplo, si se reinicia el AppPool
del IIS y se interrumpe el proceso, sea el usuario el responsable de
volver a lanzarlo. Para este último caso creo que te sería útil el
siguiente ejemplo que hice hace un tiempo en base a una importación de
datos que desarrolle en una aplicación web:
http://nelopauselli.blogspot.com/2010/09/proceso-asincronico-en-aspnet-mvc-con.html
este proceso funciona hoy en día en producción y según tengo entendido
con procesos de 10 mins o mas.
Hasta donde entiendo (que no mucho tampoco), el tema del timeout es
del explorador, el tema del server es que se reinicie el AppPool así
que tendrás que analizar estos casos.
saludos.
nelo
2011/4/18 cibrax <cib...@gmail.com>:
asp.net también limita a X threads por CPU (también configurable). Digamos que lo tengo configurado en 25 y tengo 2 CPU, eso da un total de 50 threads disponibles para atender requests simultáneos.
Si subo el timeout a 10 minutos y mando a ejecutar 8 procesos largos, voy a empezar a usar threads del pool, disminuyendo a 42 la cantidad de threads disponibles para atender nuevos requests. Si tengo mas de 42 pedidos simultáneos, se van a empezar a encolar.
Esta solución es válida pero tenés que hacer un poco de cuentas y monitoreo de los perf counters de asp.net para asegurarte que no hay encolamiento (o que por lo menos es bajo).
Respecto al uso de async pages, solo son útiles si el proceso es intensivo de I/O (ej un query muy pesado a la DB o una llamada a un webservice que tarda mucho), pero no ayuda mucho en el caso de que el proceso sea intensivo de CPU (hay que ver cual es tu caso). Tal como dice Pablo, el fire and forget no sirve si no liberás el thread (ej haciendo una llamada async a I/O).
Si podés segmentar el proceso largo en muchos procesos pequeños independientes (muchas veces no se puede por la propia naturaleza del proceso), una alternativa es manejar el proceso largo desde el browser, haciendo llamadas jquery por cada proceso pequeño. Tal como plantea Carlos, pero desde el browser. Esto es similar al enfoque que plantea Nelo pero ejecutando el proceso de a partes ( y le podés robar el código de la progressbar a Nelo :) )
Espero no haberte mareado mucho, si no queda claro preguntá, que para eso estamos!
Saludos
JorgeF
http://blog.jorgef.net
@jorgefioranelli
nelo
2011/4/18 Carlos Peix <carlo...@gmail.com>:
Ahora me quedan algunas dudas, así que a repasar código un poco viejito y contar que hice en su momento....
1) Aplicación Web que publica un webservice mediante spring (Spring.Web.Services.WebServiceExporter normalito, nada de WCF).
2) Aplicación cliente que invoca al webservice también mediante spring (era un winform, pero para el caso igualmente es un request http al servidor).
3) Una clase que era el proceso publicado en 1, que implementaba la interface
3.1) Generaba un thread y lo apuntaba a un método estático que realmente ejecutaba el proceso.
3.2) Ejecutaba el proceso ("Start" del thread, comienza a correr mi método estatico que ejecuta el proceso)
3.3) Invocaba una clase estatica para pasar el "Id" de la corrida y mantener ciertas estructuras que permitieran revisar el estado del proceso
3.4) Hacia "Start" del thread
3.5) Devolvía el Id
4) La aplicación cliente invocaba otro método del mismo webservice que devolvía el estado la corrida y actualizaba el gauge... En algún momento el retorno era "termine" y el cliente dejaba de refrescar.
Ahora, mirando el código, que ejecutaba sin problemas en hostings de los baratitos...
/---- Clase del Servidor (publicada como webservice mediante Spring) ---/
namespace SueldosProcess
{
public class ProcesoCierreMes : IProcessService<ParametroProcesoCierreMes>
{
#region IProcessService<ParametroProcesoCierreMes> Members
public Guid StartProcess(ParametroProcesoCierreMes parametro)
{
Guid ProcessId = Guid.NewGuid();
ParameterizedThreadStart threadParam = new ParameterizedThreadStart(Procesar);
Thread workerThread = StaticProcessInfo.SetProcessStart(ProcessId, threadParam);
workerThread.Start(new Object[] { ProcessId });
return ProcessId;
}
public DAOCommon.ProcesoStatusInfo GetStatusInfo(Guid ProcessId)
{
return StaticProcessInfo.getInfo(ProcessId);
}
#endregion
#region metodo estatico para procesamiento
private static void Procesar(object state)
{
Guid ProcessID = (Guid)(state as Object[])[0];
StaticProcessInfo.SetProcessCount(ProcessID, 1);
new AvanzarParametroActivo().Procesar(); //Esta es la clase del proceso
StaticProcessInfo.finishProcess(ProcessID);
}
#endregion
}
}
/---- Clase para manejar threads ---/
public static class StaticProcessInfo
{
#region privados
private static Object semaforo = new object();
private static Dictionary<Guid, ProcesoStatusInfo> dictStatusProceso = new Dictionary<Guid, ProcesoStatusInfo>();
//TODO: Despues ver como limpias estas instancias...
private static Dictionary<Guid, Thread> activeThreads = new Dictionary<Guid, Thread>();
#endregion
public static Thread SetProcessStart(Guid processId, ParameterizedThreadStart threadParam)
{
Thread retorno;
lock (semaforo)
{
retorno = new Thread(threadParam);
activeThreads.Add(processId, retorno);
ProcesoStatusInfo info = new ProcesoStatusInfo();
info.CargandoDatos = true;
info.ProcesoTerminado = false;
dictStatusProceso.Add(processId, info);
}
return retorno;
}
…. Manejo de diccionarios, en fin… nada del otro lado + algún resource que me acabo de dar cuenta que no liberaba y me hago el gil para que no se note…
Bueno, con esto básicamente lograba lanzar un proceso en un thread, dar respuesta al cliente, y luego el thread se ejecutaba en el servidor sin problemas.
Este código tenía dos grandes problemas.
a) Que se reciclara el working process en el servidor (igualmente si esto pasa caia mi thread y todos los usuarios conectados…)
b) Que tuviera más de un working process (cosa que podía controlar tranquilamente con la base de datos).
Ahora, una vez que lanzaba el thread, y dejando de lado los dos temas anteriores, ese thread no tenía ningún problema en ejecutarse en el IIS, el proceso corría sin problemas y no frenaba.
Saludos
PD: Tengo algo de código de Nelo y de un par más de lectores de este grupo :D
-----Original Message-----
From: altnet-...@googlegroups.com [mailto:altnet-...@googlegroups.com] On Behalf Of nelopa...@gmail.com
Sent: Monday, April 18, 2011 8:57 PM
To: altnet-...@googlegroups.com
>> > net-mvc-con.html este proceso funciona hoy en día en producción y
Cristian,
tiene lógica hacerlo como vos decís, pero para el batch que tengo por resolver mantener un thread vivo y encolarle un proceso es tener vivo un thread 23.45 hs esperando un mensaje (es un batch nocturno que cambiara a lo sumo 50 registros x día y sus respectivos mails).
Igualmente coincido con la idea de “si moves volumen, pedile una VPS o azure” en lugar de un shared hosting, en un entorno así hasta pensaría en un servicio con lógica asíncrona para manejar algunas cuestiones como enviar emails, etc. Pero para esta aplicación no quiero gastar más tiempo en hacer “arquitectura” que en resolver el proceso.
Saludos
Me gusto esta idea, me convenció el “legendaria”…
Quedo esto (aprovechando framework 4)
/----- Global Asax -----/
protected void Application_Start()
{
Task.Factory.StartNew(() => {
//Bucle hasta que el end marque que termino
while (!ProcessQueue.Queue.IsCompleted)
{
try
{
var proceso = ProcessQueue.Queue.Take();
proceso.Procesar();
}
catch (InvalidOperationException) { } //Cuando marco que termine de agregar, el take raisea esta excepcion y sale...
catch (Exception e) { } //La vida continua :D
}
}, TaskCreationOptions.LongRunning);
}
protected void Application_End()
{
ProcessQueue.Queue.CompleteAdding(); //Marco la cola como completa para que cierre el tread.
}
/---- “ProcessQueue” ---/
public static class ProcessQueue
{
static ProcessQueue()
{
Queue = new BlockingCollection<IProcessCommand>();
}
public static BlockingCollection<IProcessCommand> Queue {get; private set;}
}
/---- La interfaz para procesos -----/
public interface IProcessCommand
{
void Procesar();
}
/---- El código en el controller -----/
public ActionResult Index()
{
ProcessQueue.Queue.Add(new PeridicBatchCommand());//PeriodicBatchCommand implementa IProcessCommand
return View();
}
En resumen
è Creo un task usando TaskFactory de .net 4 (le doy el hint de long running)
è Uso un BlockingList para sincronizar
o Cuando el task hace ProcessQueue.Queue.Take()
§ Si hay algo, lo saca y lo procesa
§ Si no hay nada, duerme al thread esperando a que agregue algo (no tengo que usar el timer)
§ Si marco a la lista como completada, salta una excepción “InvalidOperation”, el bucle infinito se muere
è Desde los controllers simplemente agrego el proceso a la cola
è En el application_end marco la cola como completada para que se cierre el bucle infinito.
Obviamente se puede escalar mucho esta solución, pero antes de escalar algo así me parece que primero intentaría aprovechar el consejo que me dieron de usar quartz (ya maneja colas en la base, etc., parece muy bueno).
Saludos y gracias a todos
Si se cae el IIS, o simplemente decide reciclar el AppPool, no se
reactivará la aplicación hasta que llegue la siguiente petición, por
lo que dejarías de ejecutar las tareas que tengas en background.
En este artículo te lo explica bastante bien, aunque es de hace tiempo
y puede que hayan cambiado las cosas:
http://msdn.microsoft.com/en-us/magazine/cc163821.aspx
Saludos,
Juanma.
2011/4/21 Leandro de los Santos <delossant...@gmail.com>:
Phil Haack wrote a great article on the dangers of recurring background tasks in ASP.NET
http://haacked.com/archive/2011/10/16/the-dangers-of-implementing-recurring-background-tasks-in-asp-net.aspx/
http://ayende.com/blog/155489/rotten-scheduling-dont-roll-your-own#comments
Un montón de alternativas como HangFire, Quarz.net y muchas más
http://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx
https://www.jimmybogard.com/tales-from-the-net-migration-trenches-hangfire/
Hangfire is an easy way to perform background tasks/processes in a .NET web application, and it also supports persistent storage for both the jobs and queues. I use it quite a lot in applications where I don't want to introduce a separate host for processing messages, or introduce a specific queue/broker for background jobs. Hangfire supports fire-and-forget jobs as well as "cron"-based jobs. It also provides a nice dashboard where you can see completed and failed jobs, with the option of retrying failed jobs as desired.
Depending on how you're using Hangfire, it introduces a unique challenge when migrating from .NET 4.8 to .NET 6/7/8. Hangfire supports both frameworks, but as usual, the devil is in the details. We want to be able to start/consume jobs from both sides AND ensure our job executes at most once.
It's weakness in my experience was the following two areas:
1) Compared to Azure Functions... Azure Functions just scales better for speed and performance. But you pay the price in owning the environment. Hangfire dashboard is fantastic. I wish MS would build something remotely as good as the Hangfire Dashboard.
2) Multi-Tenancy is really difficult in hangfire. Haven't tried it yet in Core... But it was unreliable in ASP.Net 4.8 with random failures.
If these scenarios aren't in your use case... Hangfire is the BOMB. Love it!