Parece ser que el primer Serivce Pack para Windows Vista ya esta en Beta y estará disponible en unas semanas en TechNet y para los subscriptores de MSDN, la versión final entre finales del 2007 y finales del 2008. Ocupará 1GB y requerirá 7GB libres para la instalación ... casi nada.
Más info, en Ars Technica.
¿Era necesario ya? La gente cree que sí, y de hecho, 1GB (que además será comprimido...) es mucho parche. Personalmente solo he probado la version Home, y no me ha disgustado pero tampoco pagaría por migrar mi Windows XP Professional. En cualquier caso las cifras hablan, y el balance de vulnerabilidades en el tiempo de vida de Vista es mucho menor que XP con sus mismos días de vida, lo cual ratifica las declaraciones de Microsoft sobre que es el Windows más seguro.
Leo através del blog de Rico Mariani que JD Meier ha anunciado la versión final de la guía Performance Testing Guidance for Web Applications, que se suma al proyecto Performance Testing Guidance.
Estas guías proponen metodologías y técnicas para hacer tests de rendimiento en aplicaciones Web y Windows por medio de las herramientas que provee Visua Studio 2005 y Visual Studio 2005 Team System.
He estado echando un vistazo y ambos tienen muy buena pinta, como muestra este How To: Identificar funciones causantes de un cuello de botella por alto consumo de tiempo de usuario de CPU en aplicaciones servidor en un entorno de producción.
Merece la pena leer este trabajo.
Como ya había dicho anteriormente, hay un sin fin de lenguajes con los que se pueden programar en .NET, pero no todos siguen las mismas reglas, por ejemplo, hay unos que son case-sensitive(ie:C#) y otros case-insensitive (VB) ... Imaginad que escribo una librería .dll en C# y mi cliente la usará en su proyecto ASP.NET en VB (mis dos enemigos juntos xD) ..., imaginad que en esa librería he incluido dos métodos públicos con mismo nombre y parámetros que se diferencian en que uno empieza por mayúsculas y otro en minúsculas ... ¿que pasaría al abrirlo con VB? Pues que no podría diferenciarlo.
Para esto existe el CLS (Common Language Specification), que "...es una especificación estandarizada que describe un entorno virtual para la ejecución de aplicaciones, cuya principal característica es la de permitir que aplicaciones escritas en distintos lenguajes de alto nivel puedan luego ejecutarse en múltiples plataformas tanto de hardware como de software sin necesidad de reescribir o recompilar su código fuente..." (Vía Wikipedia)
Para ayudar a cumplir con el CLS existe el atributo a nivel de ensamblado CLSCompliant, que hará que el compilador nos avise en tiempo de diseño con "Warnings" en aquellos puntos donde infringimos alguna regla del CLS. Este es uno de esos casos donde ... no cuesta tanto hacer las cosas bien :D
Las principales reglas del CLS son:
- Los elementos públicos no pueden diferenciarse unos de otros por las mayúsculas/minúsculas.
- No se pueden usar tipos sin signo (ie: UInt16, UInt43, ..etc...).
- Las sobrecargas de métodos no pueden diferenciarse por parámetros pasados por 'ref' ó 'out'.
- Los elementos públicos no pueden comenzar con un guión bajo (underscore).
- Los operadores no pueden sobrecargarse.
Todas las reglas no afectan a elementos privados, relamente lo que importa es cuidar la forma de la funcionalidad que se va a mostrar, no lo que hay por dentro.
Es uan verdadera pena que como indica el MSDN en la nota del atributo CLSCompliant... el compilador de "Visual Basic" no indique estas advertencias... una verdadera pena.
El compilador actual de Microsoft Visual Basic no genera intencionalmente una advertencia de compatibilidad con CLS, sin embargo, una futura versión del compilador emitirá el aviso.
Una muestra que espero que sea clarificadora:
using System;
using System.Collections.Generic;
using System.Text;
[assembly: CLSCompliant(true)]
namespace CLSCompliant
{ public class MiClase
{ // 1. Los nombres de elementos públicos no pueden
// diferenciarse únicamente en las mayúsculas/minúsculas
// pues algunos lenguajes son case-insensitive.
public void Metodo1(){}
public void metodo1(){} // Warning: 'CLSCompliant.MiClase.metodo1()' differing only
// in case is not CLS-compliant
public int Propiedad1 { get { return 0; } } public int propiedad1 { get { return 0; } } // Warning: 'CLSCompliant.MiClase.propiedad1.get' differing
// only in case is not CLS-compliant
/* --------------------- */
// 2. No se pueden usar tipos "sin signo" como elementos
// públicos de una clase ó incluso una interfaz.
public uint Metodo2(){return default(UInt32);} // Warning: Return type of 'CLSCompliant.MiClase.Metodo2()'
// is not CLS-compliant
public void Metodo2(uint parametro){} // Warning: Argument type 'uint' is not CLS-compliant
/* --------------------- */
// 3. Las sobre cargas de los métodos, no pueden diferenciarse
// por parámetros 'ref' ó 'out'.
public void Metodo3(int parametro){}
public void Metodo3(ref int parametro){} // Warning: Overloaded method 'CLSCompliant.MiClase.Metodo3(ref int)'
// differing only in ref or out, or in array rank,
// is not CLS-compliant
/* --------------------- */
// 4. Los elementos públicos no pueden comenzar con un
// guión bajo (underscore).
public void _Metodo5(){} // Warining: Identifier 'CLSCompliant.MiClase._Metodo5()'
// is not CLS-compliant
/* --------------------- */
// 5. Los operadores no se pueden sobrecargar
public static MiClase operator +(MiClase operando) { return null; }
public static MiClase operator +(MiClase operando1, MiClase operando2)
{ return null; } // Warning: Aquí debería dar un aviso ... que no dá, ¿será un bug?
}
}
Prometo investigar que pasa con la sobrecarga de operadores que parece no avisar del error :S

Un
código en C# para generar el efecto Matrix en consola, lo que se aburre la gente en vacaciones, en fin, Ya puedes quedar como un auténtico
lammer hacker con tus colegas y hacer temblar a tu jefe xD
En cualquier caso, no deja de ser un experimento curioso e intersante en un sentido intrínsecamente técnico, así que no dejeis de echar un vistazo al código de esta "utilidad" :D
Hay hasta un video en YouTube.
¿Que será lo próximo?
Siguiendo con el artículo anterior donde explicaba que es el código administrado MSIL, voy a explicar ahora también de forma breve como el CLR (Common Language Runtime) ejecuta dicho código con la ayuda del JIT (Just In Time compiler), y cuales son las ventajas y características de este modelo de ejecución. Este es más extenso que el anterior, aunque está todo bastante resumido y explicado por encima espero que resulte interesante.
Como comentaba, una vez generado el código administrado se compone un ensamblado junto los metadatos, el manifiesto que lo describe y los recursos que se hayan decidido incluir en él, estos datos permiten al CLR obtener información del ensamblado, sus tipos y recuperar el código administrado cuando se requiera.
El CLR es una máquina virtual, un entorno de ejecución para nuestro código MSIL, que provee las siguientes características:
- Provee de un modelo consistente y homogéneo:
- Basado plenamente en POO.
- Todo son ensamblados, aunque se pueden usar elementos convencionales como .dll nativas, objetos COM+ y similares, siempre se realiza de una forma convenida.
- Los errores siempre se disparan en forma de Excepción.
- Es un modelo fuertemente tipado y el CLR verifica el código antes de su ejecución.
- Permite trabajar con elementos no admnistrados como librerias en código nativo ó objetos COM+ mediante interoperabilidad.
- Ejecuta todas las aplicaciones administradas dentro de un mismo espacio de direccionamiento virtual del sistema operativo de forma que se gana rendimiento al no tener que estar solicitando recursos al sistema operativo constantemente, pero cada aplicación se ejecuta en un dominio de aplicación del framework, que es un contenedor que impide que unas aplicaciones accedan a la memoria de otras, teniendo de esta forma la misma robustez y seguridad que si se ejecutasen en sus propios espacios de direccionamiento virtual.
- Elimina el problema del "dll Hell", ya que los ensamblados van firmados con su número de versión en el manifiesto, lo que permite que numerosas dll con el mismo nombre y hasta la misma funcionalidad coexistan con diferentes números de versión.
- Permite soporte multiplataforma, ya que el código administrado es independiente de la plataforma, un CLR para Linux podría ejecutar perfectamente dicho código.
- Provee de un sistema de gestión de memoria encargado de recolectar la memoria en desuso u ocupada por objetos desreferenciados, el recolector de basura (Garbage Collector).
- Soporte multithreading, que le permite ejecutar aplicaciones que trabajan con varios hilos de ejecución en paralelo.
- Diversas técnicas para mejorar el rendimiento como la cache de objetos que permite la conservación y posterior reutilización de objetos que son costosos de crear, evadiendo la recolección de basura mientras permanecen en la cache. Un ejemplo de esto es el ThreadPool
- Seguridad CAS (Code Access Security), un sistema de permisos independiente al del sistema operativo y la plataforma con el que el CLR puede evaluar si el código requerido tiene permisos para ejecutarse.
- Seguridad basada en roles (Roled Based Security), un sistema de seguridad basado en perfiles de usuario también independiente del sistema operativo aunque se puede integrar con él.
Cuando ejecutamos un ensamblado ejecutable (una aplicación, no una libreria), el CLR procede de la siguiente forma:
- Busca la entrada, normalmente la clase Program.
- Inicializa la clase inicializando todos sus campos.
- Busca el punto de entrada, normalmente el método Main.
- Detecta todos los tipos que son referenciados desde él y reserva la memoria oportuna.
- Empieza la ejecución, encuentra el primer método y lo busca en el ensamblado utilizando los metadatos.
- Procede con este método como en el punto 4.
- Carga el código administrado MSIL.
- Verifica el código MSIL asegurando que es seguro. Verifica cosas como :
- Que cada método es invocado con el número y tipo correcto de parámetros.
- Que los valores de retorno se reciben apropiadamente.
- Que cada método tiene una instrucción de retorno.
- Llama a una función interna denominada JITCompiler (Just In Time Compiler), conocido también como JIT ó JITer. Esta función es la encargada de compilar el código administrado a código nativo, y se guarda en memoria dinámica.
- Se ejecuta el código nativo. Si se vuelve a ejecutar el mismo método, directamente el JITCompiler devuelve la dirección de memoria donde se compiló anteriormente, de forma que no se necesita verificarlo ni compilarlo dos veces.
- Continua la ejecución con el siguiente método de la misma forma.
El proceso de compilación dinámica produce una perdida de rendimiento y un mayor uso de memoria dinámica, pero es mínimo, además este proceso aporta una serie de ventajas bastante interesantes, echemos un vistazo más profundo al funcionamiento del JIT:
- Compila el código administrado a código nativo y lo almacena en memoria dinámica, si se requiere usar de nuevo, se obtiene directamente el código nativo sin tener que volver a compilarlo. Cuando la aplicación termina dicho código se desecha.
- Ya que se almacena en memoria dinámica, si se ejecuta el ensamblado en otro proceso Windows distinto, no se puede aprovechar la compilación anterior y se vuelve a compilar de nuevo.
- Optimiza el código nativo al igual que un compilador C++, con la penalización de rendimiento que esto conlleva, pero la ejecución se realizará con un rendimiento mucho mayor que compensa de sobra la penalización anterior.
- Puede determinar que CPU explícita (Pentium IV, Pentium III,...etc..) tiene como objetivo en tiempo de ejecución y generar código nativo optimizado para dicho objetivo, mientras que las compilaciónes de lenguajes convencionales suelen compilar para familias genéricas. De esta forma, el código nativo generado está siempre optimizado al máximo.
- Puede optimizar el uso de variables que se encuentran en bucles mediante el uso de caches. Aunque en entornos multithreading esto puede representar un serio problema.
- Puede determinar cuando la evaluación de determinadas condiciones son siempre True ó False en la máquina donde se ejecutan (como por ejemplo, evaluar cuantos procesadores hay), obviando la generación de código nativo para estas instruciones y asignando un valor directo, con lo que se gana velocidad de ejecución.
- El CLR puede monitorizar y perfilar la ejecución del código MSIL, reorganizar y recompilar para mejorar el rendimiento en base a unos patrones de observación.
Gracias a todas estas características el decir que una aplicación en código administrado es más lenta que la misma en código nativo puede ser una afirmación precipitada, de hecho, puede ser hasta más rápida. De la misma forma, usar nGen no garantiza una ejecución más rápida, aunque si suele mejorar el tiempo de puesta en marcha. Otro día hablaré de nGen :D
Voy a intentar explicar de forma breve en varios artículos como funciona el .NET Framework y que papel juegan sus diferentes partes. En esta primera parte a modo de introducción explicaré la diferencia entre el código fuente y el código administrado generado por el compilador, dejando el CLR y el JIT para una segunda parte.
Nuestro código C# (ó cualquier otro lenguaje con el que desarrollemos en .NET... por exótico que sea), es un leguaje de alto nivel con el componemos la lógica que queremos programar.
Aquí se puede ver una extensa lista de los lenguajes con los que se puede programar en .NET.
En lenguajes convencionales no administrados, el compilador es el encargado de generar código binario final a partir del código fuente, dicho código binario también se denomina código nativo, y es el que el procesador de nuestro ordenador puede interpretar.
Este código se genera para una determinada arquitectura, como pueden ser x86, x64 ó IA64 y en determinados casos para un determinado tipo de procesador. El hecho de compilarlo para una determinada arquitectura ó procesador como objetivo, permite optimizar el código al máximo aprovechando las optimizaciones y/ó juegos de instruciones específicos de ese objetivo. El problema, es que solo existe compatibilidad hacia atrás y no en todos los casos, por lo que lo que se compile para x64 no funcionará en x86 y si por ejemplo compilasemos explícitamente para Intel PIV, ejecutarlo en un Intel PIII podría causar problemas inésperados y difíciles de seguir al intentar por ejemplo, ejecutar instrucciones que no existen en el juego de instrucciones de Intel PIII.
En los lenguajes administrados, el compilador de nuestro IDE genera un código intermedio, a mitad de camino entre el lenguaje ensamblador y el lenguaje de alto nivel (más próximo al segundo que al primero) que se usará a posteriori para generar el código nativo que entiende nuestro procesador. En .NET este código es denominado código administrado, CIL (Common Intermediate Language) ó MSIL (Microsoft Intermediate Language). El compilador genera lo que se demomina ensamblado ó ensamble, donde además de nuestro código administrado se añade un manifiesto que es una descripción el esamblado, los recursos que hemos decidido incluir dentro del ensamblado y los metadatos, que son datos que describen los tipos, métodos y campos que lo componen.
Podemos ver el código administrado de un ensamblado mediante la herramienta Ildasm.exe.
Entre las principales ventajas de este método destacan ser independiente del lenguaje elegido y de la plataforma, de forma que cualquier lenguaje de .NET genera el mismo código administrado y este es independiente de la arquitectura donde se ejecute, a no ser que se indique explícitamente. En ocasiones, el ensamblado puede requerir de código nativo que sea dependiente de la plataforma, en las que podemos indicar que generamos en ensamblado para una determinada arquitectura, de forma que el .NET Framework nos avise de ello si intentamos ejecutarlo en otra distinta, pero esto no genera ninguna optimización especial del código administrado, simplemente es una restricción.
Este código es convertido a código nativo en tiempo de ejecución por medio del JIT, del que hablaré próximamente.
Bueno, por fin después de varios días trabajando con la versión en desarrollo de BlogEngine.NET para realizar unos cambios y siguiendo los bugs que han ido solventando, tengo las características que necesito como para plantarme e inaugurar el blog !! :D Si alguien ve algún error que me mande un email.
Ahora, http://www.vtortola.net redirecciona allí siempre.
A partir de ahora bloggearé aquí, e iré recuperando algunos de los artículos que postee en mi antiguo blog en WordPressy mi blog en ElBruno, aunque en este seguiré hanciéndolo.
Ahora gracias al buen soporte para Live Writer, puedo poner código de forma más sencilla que con WordPress y con mejor resultado:
private bool canThreadIt()
{ bool result = false;
if (Thread.VolatileRead(ref runThreads) < maxThreads)
{ Interlocked.Increment(ref runThreads);
result = true;
}
return result;
}
Pues lo dicho, aquí me encontrareis :D
vtortola.NET
- Los métodos de los objetos remotos se ejecutan de forma similar a como se hace en un objeto convencional pero se ejecutan en el servidor ó en el cliente dependiendo de si un objeto remoto se activa en el cliente ó en el servidor.
- Se pueden realizar invocaciones asíncronas de métodos de un objeto remoto creando un delegado que cumpla la firma de dicho método y llamando a BeginInvoke, de esta forma podemos beneficiarnos de todas las ventajas del modelo asíncrono.
- Mediante el atributo OneWay podemos implementar métodos con funcionalidad 'fire and forget' ("dispara y olvida" .. estos yankis.. siempre disparando...). Aunque este atributo protege al cliente de problemas en el servidor ó en la comunicación, no protege al servidor de exepciones en el código.
- Si se usan interfaces en el ensamblado compartido entre cliente y servidor, el atributo ha de colocarse tanto en la interfaz como en la implementación (Recuerda, una interfaz simplemente es un contrato), porque Visual Studio 2005 no nos los pone al implementar la interfaz en la clase y el cliente no sabría que es 'OneWay'.
public interface IInterfaz
public class Clase : IInterfaz
#region IInterfaz Members
throw new Exception("The method or operation is not implemented.");
throw new Exception("The method or operation is not implemented.");
- Como siempre en el modelo asíncrono existen tres modelos de invocación, el primero, no indicando un AsyncCallback ni controlando cuando ha terminado la ejecución, el segundo, controlando la propiedad .IsCompleted para saber si ha acabado, y por último el tercero y más elegante, usando un AsyncCallback que nos informará de cuando ha terminado la ejecución.
- Se pueden usar eventos en objetos remotos de forma similar a como se usan en objetos convencionales. De esta forma se provee de una comunicación bidirecional donde el cliente también hace de servidor y procesa acuses de recibo.
- Debe utilizarse con mucha precaución. Reduce la escalabilidad, aumenta la complejidad del manejo de excepciones ya que ambas partes son servidor y cliente, las desconexiones pueden ser un problema ya que al reconectar se subscribe una nueva clase proxy al evento y el anterior queda ahí también, CAS no funciona entre procesos.
- Nota Personal: Puedo dar fé del auténtico coñazo que es trabajar con eventos en remoting hasta que le cojes la forma. Lo más curioso es que aunque el libro menta el problema de los subscriptores fantasma a los eventos y el MSDN también ... no aporta soluciones. Si alguien tiene interés ... el artículo que fue mi faro en la penumbra sobre el tema .NET Remoting - Events. Events? Events!
- Cualquier tipo (delegados, estructuras, clases,...) que vaya a utilizarse para comunicarse entre servidor y cliente debe ser serializable. Para ello se puede ornamentar con el atributo [Serializable].
- Remoting es una tecnología distribuida que permite pasar tipos de datos ricos
y con fundamento de un dominio de aplicación a otro. Es más efectivo cuando el cliente y el servidor funcionan en .NET. - .NET 2.0 provee de tres tipos de objetos remotos:
- Singleton: Se activa en el servidor y se usa cuando hay que mantener una información de estado entre llamadas y clientes. En terminos de rendimiento es mucho mejor ya que mantener el estado es mucho menos costoso que crear nuevos objetos cada vez.
- SingleCall: Se activa en el servidor y se usa cuando no es necesario mantener un estado. Útil cuando se ha de balancear la carga entre los objetos. Se ejecuta un método por llamada y cuando se termina se destruye.
- Client-activated: Se activa a petición del cliente obteniendo un objeto dedicado. Estos objetos son gestionados con una concesión de tiempo de vida que asegura que son destriuidos y no ocupan recursos innecesariamente.
- Hay cuantro formas de hospedar un objeto remoto:
- Aplicación de consola: Simple pero tosco y necesita se ejecutada manualmente.
- Aplicación WinForms: Más visual pero también necesita más trabajo y también necesita ser ejecutada manualmente.
- Aplicación Web: Puedes aprovecharte de todas las características de IIS y de la programación con ASP.NET pero claro, necesita IIS.
- Servicio Windows: Mejor gestión de la aplicación en si pero es más complicado de depurar (Attach to process ;) ).
- La aplicación que hospeda el objeto remoto es la responsable de registrar dicho objeto y el canal de comunicación.
- Cuando se activa un objeto remoto, se genera un objeto proxy. Cuando y como depende de si se activa en el cliente ó en el servidor.
- Hay tres canales sobre los que se pueden establecer comunicación con objetos remotos:
- TCP: Con sockets y formateo binario, es el más rápido. Recomendado para redes locales.
- HTTP: Sesiones http con formateo SOAP. Ideal para comunicación en internet.
- IPC: Interprocesses Communication. Seguridad basada en ACLs. Para usar en la misma máquina.
- El único requisito para poder usar una clase como objeto remoto, es derivarla de MarshalByRefObject.
- La clase RemoteConfiguration se usa para configurar de forma programática la aplicación servidora Remoting. Con esta clase se puede configurar las propiedades básicas del objeto remoto como el nombre de aplicación.
- Para registrar objetos remotos activados en el servidor se usa RemotingConfiguration.RegisterWellKnowServiceType:
//Registro objeto remoto Singleton
RemotingConfiguration.RegisterWellKnownServiceType
(typeof(Server.MyRemoteClass), "MyRemoteClass1",
WellKnownObjectMode.Singleton);
//Registro un objeto Client-activated
RemotingConfiguration.RegisterActivatedServiceType
(typeof(Server.MyRemoteClass));
- Si existen multiples versiones disponibles de un objeto remoto y quieres especificar una versión distinta a la última... necesitas especificar dicha versión cuando registras el objeto.
- Los canales de comunicación se registran con ChannelServices.RegisterChannel y una instancia del canal que se quiere registrar.
IChannel tcp = new TcpServerChannel(8090);
ChannelServices.RegisterChannel(tcp, false);
IChannel http = new HttpServerChannel(8006);
ChannelServices.RegisterChannel(http, false);
IChannel ipc = new IpcServerChannel("ipcchannel");
ChannelServices.RegisterChannel(ipc, false);
- Se puede configurar el objeto remoto desde el archivo de configuración de la aplicación, pero hay que indicar el nombre del archivo:
RemotingConfiguration.Configure("MyApp.exe.config", false);
- Visual Studio nos abstrae mucho trabajo al crear aplicaciones que consumen servicios web creando clases proxy que nos permiten trabajar con los métodos remotos como si de una clase en una dll convencional se tratase.
- Podemos añadir una haciendo un 'Add Web Reference' y pasandole el WSDL junto el nombre que queremos darle a la referencia. Usando el ejemplo de WS del resumen anterior, he creado una llamada 'referenciaWeb' y usarla en el código es tan simple como esto:
using System.Collections.Generic;
using TestWebServiceConsumer.referenciaWeb;
namespace TestWebServiceConsumer
static void Main(string[] args)
TestWebService test = new TestWebService();
Console.WriteLine(test.ProbandoObjetoApplication().ToString());
- Visual Studio genera una serie de archivos, entre ellos Reference.cs que contiene el código de dicha clase proxy que utilizaremos para comunicarnos con el servicio web:
- En caso de que cambie el servicio web, es necesario realizar un 'Update Web Reference' para volver a generar la clase proxy y de esta forma actualizar sus miembros. Procura no realizar cambios demasiado bruscos cuando se trate de modificar un servicio web, sobre todo no cambies las firmas de los métodos.
- Podemos invocar servicios web de forma asíncrona siguiendo el modelo event-driven (basado en eventos) gracias a los métodos que la clase proxy genera para ellos (estos chicos de Microsoft, están en todo!!... menos en los bugs xD). El método con el sufijo 'Async' se usa para invocar de forma asíncrona y el evento con el nombre del método y el sufijo Completed es al que nos debemos suscribir para recibir los resultados. También se crea una clase <NombreDelMetodo>CompletedEventArgs donde nos llegarán los datos y otra información útil. Podemos canclear una operación asíncrona pasandole el userState de la operación. El ejemplo plasma todo esto:
static void Main(string[] args)
TestWebService test = new TestWebService();
test.ProbandoObjetoApplicationCompleted +=
new ProbandoObjetoApplicationCompletedEventHandler(test_ProbandoObjetoApplicationCompleted);
// Lanzo dos operaciones asíncronas
test.ProbandoObjetoApplicationAsync(i);
static void test_ProbandoObjetoApplicationCompleted(object sender, ProbandoObjetoApplicationCompletedEventArgs e)
if (!e.Cancelled) // Todo fue correcto :)
Console.WriteLine("{0} devuelve {1}", e.UserState, e.Result);
else if (e.Error != null) // Hubo una excepción!!
Console.WriteLine("{0}:{1}",e.Error.GetType().Name, e.Error.Message);
else // Se canceló la operación programáticamente
Console.WriteLine("{0} Cancelada!!", Convert.ToInt32(e.UserState));
- Siempre es recomendable usar este modelo, ya que si una excepción no manejada sucediese en el servicio web, el cliente no se enteraria y quedaría esperando hasta el timeout.
- Podemos hacer que los clientes de un servicio web no tengan que esperar a la finalización de un método web indicando en este que es 'OneWay' como se indicaba en el resumen anterior, de esta forma ni valores de retorno ni excepciones pueden ser devueltas, lo cual puede enmascarar problemas más graves.