En WCF existen varias formas para consumir un servicio de forma asincrónica. La más simple quizás es poner el asincronismo del lado del cliente, que es lo que vamos a hacer en este post.
Para iniciar vamos a crear un proyecto del tipo WCF ServiceLibrary y vamos a crear un servicio muy simple que tiene solamente un método que retorna el número de frutas – strings – que se lo soliciten vía parámetro a la operación. La interface – contrato – del servicio se ve a continuación:
[ServiceContract(Namespace="http://drojasm.net")] public interface IServicioProductos{ [OperationContract] List<string> ObtenerProductos(int pCantidad); }
Seguidamente procedemos con el código para implementar la operación. Como podemos ver en el siguiente código, este simplemente lo que hace es hacer un for y agregar un string a la lista por cada iteración. Nótese además la línea en donde ponemos a dormir el thread del servicio, esto con el fin de que nos de tiempo de hacer algo diferente en el UI mientras el servicio se ejecuta.
public class ServicioProductos : IServicioProductos { public List<string> ObtenerProductos(int pCantidad) { List<string> _productos = new List<string>(); for (int i = 0; i < pCantidad; i++) { _productos.Add("Fruta Número " + i.ToString()); } System.Threading.Thread.Sleep(5000); return _productos; } } }
Ahora vamos a proceder a crear el cliente que va a consumir el servicio. En este caso vamos a utilizar una aplicación WPF. Lo primero que vamos a hacer después de crear la aplicación es agregar una referencia al servicio como se hace tradicionalmente; es decir, botón derecho sobre el proyecto, seleccionar agregar referencia, y en la pantalla de configuración de la referencia del servicio, poner la dirección del servicio – en este caso hosteado en el wcfsvchost – y por último obtener el wsdl del mismo.
Sin embargo, esta vez vamos a seleccionar además el botón de “Advanced” para configurar la generación del proxy. Luego en esta pantalla vamos a configurar la generación del proxy, seleccionando en esta la opción para generar clientes asincrónicos. Esta opción nos va a generar además de los métodos tradicionales sincrónicos, los métodos necesarios para invocar el servicio utilizando asincronismo.
Luego seleccionamos Ok en las siguientes dos pantallas y se procede con la generación del proxy. Como podemos ver en la siguiente figura, el proxy genera los métodos para consumir asincrónicamente el servicio.
Ahora procedemos a crear la pantalla en WPF para invocar el servicio. Para esto, vamos a agregar un listbox en donde pintamos el resultado de cada servicio. Además vamos a poner un textbox para que el usuario – yo
- digite cuantos elementos quiere en la lista. También vamos a poner dos botones, uno para invocar el servicio sincrónicamente y otro asincrónicamente. Por último, vamos a poner un botón que va a lanzar un MessageBox cuando se le da click; el objetivo de este es demostrar que cuando invocamos el servicio de forma asincrónica, podemos llevar a cabo otras tareas mientras que cuando lo hacemos de forma sincrónica la pantalla se bloquea. El XAML de la pantala es el siguiente:
<Window x:Class="ClienteAsincronicoWCF.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="447"> <Grid> <ListBox Height="197" HorizontalAlignment="Left" Margin="47,74,0,0" Name="lstFrutas" VerticalAlignment="Top" Width="120" /> <Label Content="Frutas" Height="28" HorizontalAlignment="Left" Margin="47,40,0,0" Name="label1" VerticalAlignment="Top" /> <Button Content="Cargar Sincronicamente" Height="23" HorizontalAlignment="Left" Margin="213,220,0,0" Name="btnSincronico" VerticalAlignment="Top" Width="140" Click="btnSincronico_Click" /> <Button Content="Cargar Asincronicamente" Height="23" HorizontalAlignment="Left" Margin="213,249,0,0" Name="btnAsincronico" VerticalAlignment="Top" Width="140" Click="btnAsincronico_Click" /> <Label Content="Cantidad de Productos" Height="28" HorizontalAlignment="Left" Margin="213,146,0,0" Name="label2" VerticalAlignment="Top" /> <TextBox Height="23" HorizontalAlignment="Left" Margin="213,180,0,0" Name="txtCantidad" VerticalAlignment="Top" Width="140" /> <Button Height="75" HorizontalAlignment="Left" Margin="269,40,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click"> <Button.Content> <StackPanel> <Image Height="48" Name="image1" Stretch="Fill" Width="48" Source="/ClienteAsincronicoWCF;component/pens.png" /> <Label Content="Otra Tarea" Height="28" Name="label3" /> StackPanel> Button.Content> Button> Grid> Window>
Y la pantalla en modo de diseño es la siguiente:
El código del botón para la invocación sincrónica del servicio es el siguiente:
private void btnSincronico_Click(object sender, RoutedEventArgs e) { lstFrutas.ItemsSource = null; ServicioProductosClient _proxy = new ServicioProductosClient(); lstFrutas.ItemsSource = _proxy.ObtenerProductos(int.Parse(txtCantidad.Text)); }
No olvidar agregar el namespace del servicio – el que digitamos en la pantalla para agregar la referencia al mismo.
using ClienteAsincronicoWCF.ReferenciaServicioProductos;
Como podemos ver en el código del botón, la forma de invocar este servicio es la tradicional; este procedimiento bloquea la pantalla y no permite realizar ninguna otra tarea mientras la invocación al servicio se esta ejecutando.
Ahora procedemos a agregar el código de la llamada asincrónica.
private void btnAsincronico_Click(object sender, RoutedEventArgs e) { lstFrutas.ItemsSource = null; ServicioProductosClient _proxy = new ServicioProductosClient(); AsyncCallback _callBack = delegate(IAsyncResult pResult) { this.Dispatcher.BeginInvoke((Action)delegate { lstFrutas.ItemsSource = _proxy.EndObtenerProductos(pResult); }); }; _proxy.BeginObtenerProductos(int.Parse(txtCantidad.Text), _callBack, _proxy); }
En este código nos vamos a detener un momento para analizarlo un poco mas detalladamente. En primera instancia procedemos a crear un delegate en donde vamos a obtener la respuesta del servicio. En este delegate además, debemos hacer un llamado a la operación BeginInvoke porque el thread en donde se invoca el proceso es diferente al thread del UI, por lo tanto no se puede asignar el resultado directamente a la lista. Por último, invocamos el servicio de forma asincrónica utilizando el método BeginObtenerProductos.
En el botón del MessageBox tenemos el siguiente código:
private void button1_Click(object sender, RoutedEventArgs e) { MessageBox.Show("Iniciando otra operación"); }
Ahora procedemos a ejecutar el servicio con el botón btnAsincronico. Como podemos ver en la siguiente imagen, se pudo invocar al messageBox mientras el servicio estaba procesándose.