viernes, 15 de febrero de 2008

ReportDocument: "No se ha podido cargar el informe"

Una nueva entrega de errores en Visual Studio y cómo evitarlos, aunque esta vez se trata de una mala utilización del componente ReportDocument -empleado para mostrar informes en Crystal Report-. O mejor dicho, trataré de cómo evitar las malas prácticas de programación. Por decirlo fino.

Tenemos una aplicación web desarrollada en ASP.NET en la que mostramos información sobre albaranes de envío a través de un Crystal Report. Esta aplicación está publicada en una intranet donde, en determinados momentos, puede llegar a haber hasta 40 usuarios mas o menos simultáneos.

Después de hacer los desarrollos y probadas todas las opciones sin ningún error, decidimos pasar el sistema a producción. Pero, oh campos de soledad, oh mustios collados, resulta que después de llevar unos días empieza a dar un error de lo más raro:

No se ha podido cargar el informe.

Primer problema: Estábamos capturando la excepción y mostrando únicamente la información de la propiedad "Message". Esto a veces está muy bien, pero en otras la información contenida aquí no dice nada, y nos hace falta saber mas, es decir, "InnerException".

Este error tenía unos efectos colaterales nada desdeñables, puesto que a partir de este momento ya no se podía mostrar ningún otro informe, ni en esta sesión ni en ninguna del mismo usuario o de otros, teniendo que reiniciar el IIS -el día que deje de funcionar lo de salir y volver a entrar no se qué vamos a hacer-.

Reproducir el error en desarrollo era bastante complicado porque no sabíamos bajo qué condiciones se daba, así que lo siguiente fue sacar toda la información del error. Como ya he dicho, fue en "InnerException" donde empezamos a encontrar la luz al final del túnel con este mensaje:

Se ha alcanzado el límite máximo de tareas de procesamiento de informes configuradas por el administrador del sistema.

Genial. Desde el inicio ya había un cierto tufillo a recursos no liberados, y este mensaje parece que va por ahí. Próximo paso, ver si estamos liberando todo lo que utilizamos. Después de una búsqueda rápida no encontramos en todo el código ningún Dispose() ni ningún Close() del objeto que utilizamos para mostrar el informe. Mecachis.

Lo que faltaba era cerrar el ReportDocument pero claro, la programación web no tiene mucho que ver con la basada en formularios, y no siempre tenemos claro cuándo se producen los eventos. Además tenemos los famosos postback, que hace necesario entender cómo funcionan para no andar repitiendo procesos que ralentizan la carga de la página sin necesidad.

Así pues, ¿dónde cerramos el ReportDocument? No puede ser en el mismo procedimiento que lo crea, porque todavía no lo ha mostrado. El sitio natural es en el evento Page_Unload, ya que es el momento en el que termina de procesarse la página... cuyo concepto es algo parecido al evento Leave de un Windows.Form -insisto, parecido-. Es decir, algo como:


protected void Page_Unload(object sender, EventArgs e)
{

if ((informe != null) && informe.IsLoaded)

informe.Close();

}

[Actualización 06/11/2008: Además de informe.Close(), no estaría demás hacer también informe.Dispose(), tal y como me han dicho en varios comentarios de esta entrada. :-)]

"informe" es una variable del tipo ReportDocument privada de la página. En este fragmento de código se puede ver cómo primero comprobamos si está instanciada -no lo estará si no hemos encontrado el albarán de carga buscado- y después comprobamos si tiene datos cargados -esta propiedad cambia a true al utilizar el método SetDataSource(), entre otros-.

Con esto terminaron nuestros quebraderos de cabeza, al menos en lo relativo a este error.


PD: Al que hizo esa página le tenemos escribiendo 1024 veces "Liberaré los recursos cuando termine de utilizarlos". :-)

27 comentarios:

Anónimo dijo...

Y el que esté libre de pecado que tire la primera piedra...

De este tema tengo yo un ejemplo muy majo de una aplicación que costó una pasta, y que dejaba de funcionar a las 09.00 de la mañana (aproximadamente). O sea, cuando se habían conectado 5 usuarios...

Para "solucionarlo", había que "bajarse y volverse a subir" del Tomcat.

Y yo mismo tengo "colgada" alguna cosilla por ahí que le pasa o ha pasado algo parecido...

}:->

Rafa

Alexis dijo...

Pero que pasa si el usuario, presiona el back en el navegador??
jamas pasa por el un_load,
y el reportdocument sigue en memoria o me equivoco??

JoseMa Perez dijo...

Buenas Alexis.


Vaya por delante que no he trabajado mucho con programación web ni aspx, así que me reservo el derecho a meter la pata hasta la ingle. :-)

Realmente ya ha pasado por el unload. Como decía la filosofía de la programación web es muy diferente a la programación "tradicional", y si escribimos algo de código tanto en Page_Load como en Page_Unload y ponemos unos puntos de parada vemos que el proceso que hace es:

Page_Load ---> Page_Unload ---> Muestra la página.

Cuando por fin tenemos la pantalla a la vista el proceso de ejecución ya ha pasado por Page_Unload y ha liberado los recursos.

Si volvemos a la página previa con Back da lo mismo si pasa o no pasa por Page_Unload (vale, no da lo mismo y de hecho creo que efectivamente no pasa), porque la memoria ya está liberada.

Espero que sirva de ayuda. :-)


Un saludo, o dos.

Anónimo dijo...

Josema.

Gracias por su publicación, creo que me acaba de quitar un dolor de cabeza.

Unknown dijo...

Hola, pues fíjate, estaba buscando soluciones a ese mismo error. He visto que en el Unload, se puede meter:

ejemploCrystalReportViewer.Dispose()
ejemploCrystalReportViewer = Nothing
ejemploRpt.Close()
ejemploRpt.Dispose()

Esta información la he sacado de aqui.


De todos modos, me preocupa el no saber si esa es la única solución.

Buen aporte amigo.

Unknown dijo...
Este comentario ha sido eliminado por el autor.
Unknown dijo...

Hola, perdona, pero no quiero ser un pesado. Mira, me metí en el foro de SAP (que compraron Bussiness Objects) y metí un hilo en el foro, llamado "The maximum report processing jobs limit configured by your system".

Dan otro tipo de respuesta que me gusta por ser un manager de SAP quien la responde. Bien hay que decir que la tuya (en realidad, la que posteé más arriba) la he probado, con un estresador web, y soluciona perfectamente el problema.

Un saludo.

JoseMa Perez dijo...

Muchas gracias shanty por compartir esa otra solución. Te lo agradezco mucho.

Con contestación en los foros de SAP no puedo verla, parece que hay que estar registrado. :->

Unknown dijo...

No se puede?? Pues fácil, te copio y pego la solución lo que me dicen:

El jefe de SAP me dice
"The system is being overloaded with print jobs (the default is 75).

You can try upping it in the registry of the web server:
HOKEY_LOCAL_MACHINE\SOFTWARE\BUSINESS OBJECTS\SUITE 12.0\REPORT APPLICATION SERVER\SERVER\Printability from 75 to say 500 and test it. Make sure it does not adversely affect your system performance when doing this.

Jason"

Con respecto a los foros, mira ver bien, porque sin logarme puedo verlos sin problemas.

Un saludo.

JoseMa Perez dijo...

Muy buenas


Juer, esa solución no es muy buena, ¿no te parece?

Yo creo que el problema no es que haya definidos pocos procesos, sino que los que se abren, no se liberan correctamente.

Creo que era mucha mejor solución la primera que pusiste (Close + Dispose), porque así solucionas el problema (que a fin de cuentas es un error de desarrollo) sin tener que tocar parámetros de configuración.


Gracias otra vez por tu aportación. :-)

Unknown dijo...

Ya tío, pero creo que la voy a tener que meter por ahi. Ima gina, .estoy desarrollando un ERP web, de modo que el tema de mostrar informes, está arreglado con el close y dispose, pero hay otra cosa. Y es que, a veces llamamos a una librería que lo que hace es directamente exportar el informe a PDF, sin vista preliminar ni nada. Estamos usando las librerías de crystal para exportar y claro, el error de ante sale, pero como tiramos de una clase que ni hereda el IDisposable no es de tipo webform, no hay manera ni de meterle el unload o un dispose. Y tío, cambiar TODO el código en el que se le hace la llamada... porque alcro, se le podría implentar el IDisposable, a cambio de añadior el dispose en los trozos (infinitos) de código al que se le está llamando. De modo que mira, mientras escribo estas líneas, en el otro monitor estoy estresando la aplicación para ver si la solución del pavo de SAP vale.

Yo estoy de acuerdo contigo, y de hecho así se lo he hecho saber en el foro.

Encantado tío.

Anónimo dijo...

Esta tecnica aplica para los windows forms.
Desarrolle una aplicacion en VB.Net que imprime cerca de 500 tickets diarios y cada hora y media me daba el problema. Aplique la formula Close + Dispose y ahora almuerzo tranquilo.

JoseMa Perez dijo...

Muy buenas


Genial, voy a actualizar la entrada para poner también Dispose, como también dijo Shanty en su comentario.

Gracias por la aportación! :-)

GoTiKo dijo...

Muchas gracias por informar tan detalladamente sobre este problema.

Pero me temo que en mi caso no me da resultado, a no ser que lo esté haciendo mal. Es en VB.net

Dentro del evento UnLoad:
CrystalReportViewer1.Dispose()
CrystalReportViewer1 = Nothing

If Not IsDBNull(Reporte) And Reporte.IsLoaded Then

Reporte.Close()
Reporte.Dispose()
End If

JoseMa Perez dijo...

Muy buenas GoTiKo.

Pues así a simple vista parece que está bien. ¿Has comprobado con el "debug" que está pasando por ese fragmento de código? ¿No tendrás otros informes en la misma página?


Un saludo.

Kondor dijo...

No es necesario llamar a Dispose() despues de llamar a Close(). De hecho el método Close() en la mayoría de los casos no suele ser más que un wrapper para el Dispose() con el objetivo de hacerlo más claro de leer.

Esto significa que se está llamando al Dispose() dos veces seguidas, cosa que no falla pero que tampoco hace falta.

Anónimo dijo...
Este comentario ha sido eliminado por un administrador del blog.
GoTiKo dijo...

Así es JoseMa, realmente utilizaba 3 informes dependiendo del usuario que accedía.

Muchas gracias. Y siento la "pequeña tardanza" en responder. jeje

Naúfrago del Asfalto dijo...

Hola, he implementado las dos soluciones propuestas y me sigue pasando, alguien puede dar alguna otra solución?. Trabajo con VS2010 y CR for .NET framework 4.0
Gracias.

Ileana Bonilla dijo...

Hola , A mi me esta pasando lo mismo, una aplicación en .net de Visual Studio 2010 y reportes con Crystal, los reportes corren bien en el servidor y cuando cada usuario (son 9) ha impreso unos 10 reportes cada uno , todos los reportes se bloquean, y da ese mensaje de "no se pudo cargar el informe" , puse el dispose y el close en el page_unload, y tambien cambie el key de PrintJobs en el registry del servidor de 75 a 500.. y sigue pasando.. ya no se que hacer !! :( ayuda por favor

JoseMa Perez dijo...

Ya lo siento, pero es que ni idea.

En cualquier caso últimamente hemos tenido mil problemas con Crystal y un compañero de trabajo los ha solventado, le voy a pedir que se pase por aquí a ver si suena la flauta... :-)

Ileana Bonilla dijo...

Gracias ! ojala me puedan ayudar ya tengo locos a todos mis amigos por aqui con este rollo

Ileana Bonilla dijo...

nada todavia :(... ?

Anónimo dijo...

Muchas gracias por la solución, fue de mucha utilidad para solucionar un problema que tenía en la generación de un reporte.

Unknown dijo...

esto me resolvió mi problema

Hi, I solved giving permissions of write to the folder c:\windows\temp, the place where the reports stay temporarily.

le puse lectura y escritura

Unknown dijo...

Hola , A mi me esta pasando lo mismo, una aplicación en asp.net de Visual Studio 2013 y reportes con Crystal, los reportes corren bien en el servidor y cuando los usuario (son 70) ha impreso unos 10 reportes cada uno , todos los reportes se bloquean, y da ese mensaje de "no se pudo cargar el informe" , puse el dispose y el close en el page_unload, y tambien cambie el key de PrintJobs en el registry del servidor de 75 a 500.. y sigue pasando.. ya no se que hacer !! :( ayuda por favor

Unknown dijo...

Alguna Solucion