11

Estoy escribiendo una aplicación en red.Uso de la Biblioteca de tareas paralelas en un patrón asíncrono basado en eventos

Los mensajes se envían a través del transporte como tal:

Network.SendMessage (new FirstMessage()); 

me puedo registrar un controlador de eventos que se llamará cuando este tipo de mensaje llega, así:

Network.RegisterMessageHandler<FirstMessage> (OnFirstMessageReceived); 

Y el evento se activa :

public void OnFirstMessageReceived(EventArgs<FirstMessageEventArgs> e) 
{ 
} 

estoy escribiendo un procedimiento de autenticación personalizado para mi aplicación en red, que requiere alrededor de cinco mensajes para completar.

sin utilizar las bibliotecas de tareas en paralelo, que se verían obligados a codificar el siguiente paso de cada procedimiento en el controlador de eventos anterior, así:

public void OnFirstMessageReceived(EventArgs<FirstMessageEventArgs> e) 
{ 
    Network.SendMessage(new SecondMessage()); 
} 

public void OnSecondMessageReceived(EventArgs<SecondMessageEventArgs> e) 
{ 
    Network.SendMessage(new ThirdMessage()); 
} 

public void OnThirdMessageReceived(EventArgs<ThirdMessageEventArgs> e) 
{ 
    Network.SendMessage(new FourthMessage()); 
} 

public void OnFourthMessageReceived(EventArgs<FourthMessageEventArgs> e) 
{ 
    // Authentication is complete 
} 

no me gusta la idea de saltar alrededor de la código fuente para codificar una parte de esto y una parte de eso. Es difícil de entender y editar.

He oído que la Biblioteca de tareas paralelas simplifica sustancialmente esta solución.

Sin embargo, muchos de los ejemplos que leí utilizando la Biblioteca de tareas paralelas estaban relacionados con el inicio de una cadena de tareas activas. Lo que quiero decir con 'activa', es que cada tarea podría comenzar cuando se le llama de forma explícita, así:

public void Drink() {} 
public void Eat() {} 
public void Sleep() {} 

Task.Factory.StartNew( () => Drink()) 
      .ContinueWith(() => Eat() ) 
      .ContinueWith(() => Sleep()); 

Esto es lo contrario de mi patrón asincrónico basado en eventos, en los que cada método de control de eventos sólo se llama cuando el mensaje es recibido

En otras palabras, no se puede hacer algo como esto (pero quiero):

Task.Factory.StartNew( () => OnFirstMessageReceived() ) 
      .ContinueWith(() => OnSecondMessageReceived()) 
      .ContinueWith(() => OnThirdMessageReceived() ) 
      .ContinueWith(() => OnFourthMessageReceived()); 

He leído this article, pero no acabo de entenderlo. Parece que lo que necesito tiene que ver con TaskCompletionSource. Si quisiera realizar una tarea desde mi patrón asíncrono basado en eventos, como el bloque de código anterior, ¿cómo se vería?

Respuesta

20

Tiene razón acerca de TaskCompletionSource, es la clave para transformar EAP (patrón asíncrono basado en eventos) a la Tarea de TPL.

Esto está documentado aquí: https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/tpl-and-traditional-async-programming#exposing-complex-eap-operations-as-tasks

Este es el código simplificado:

public static class Extensions 
{ 
    public static Task<XDocument> GetRssDownloadTask(
     this WebClient client, Uri rssFeedUri) 
    { 
     // task completion source is an object, which has some state. 
     // it gives out the task, which completes, when state turns "completed" 
     // or else it could be canceled or throw an exception 
     var tcs = new TaskCompletionSource<XDocument>(); 

     // now we subscribe to completed event. depending on event result 
     // we set TaskCompletionSource state completed, canceled, or error 
     client.DownloadStringCompleted += (sender, e) => 
     { 
        if(e.Cancelled) 
        { 
         tcs.SetCanceled(); 
        } 
        else if(null != e.Error) 
        { 
         tcs.SetException(e.Error); 
        } 
        else 
        { 
         tcs.SetResult(XDocument.Parse(e.Result)); 
        } 
     }; 

     // now we start asyncronous operation 
     client.DownloadStringAsync(rssFeedUri); 
     // and return the underlying task immediately 
     return tcs.Task; 
    } 
} 

Ahora, todo lo que tiene que hacer, para hacer una cadena de esas operaciones, es sólo para establecer sus continuaciones (que no es muy cómodo en este momento, y la esperan ser C# 5 y asíncrono ayudará mucho con él)

por lo tanto, este código podría ser utilizado como esto:

public static void Main() 
{ 
    var client = new WebClient(); 

    client.GetRssDownloadTask(
     new Uri("http://blogs.msdn.com/b/ericlippert/rss.aspx")) 
     .ContinueWith(t => { 
      ShowXmlInMyUI(t.Result); // show first result somewhere 
      // start a new task here if you want a chain sequence 
     }); 

    // or start it here if you want to get some rss feeds simultaneously 

    // if we had await now, we would add 
    // async keyword to Main method defenition and then 

    XDocument feedEric = await client.GetRssDownloadTask(
     new Uri("http://blogs.msdn.com/b/ericlippert/rss.aspx")); 
    XDocument feedJon = await client.GetRssDownloadTask(
     new Uri("http://feeds.feedburner.com/JonSkeetCodingBlog?format=xml")); 
    // it's chaining - one task starts executing after 
    // another, but it is still asynchronous 
} 
+0

Este es un gran consejo, y realmente me permitió trabajar con WebClient mucho más fácil. ¡¡Gracias!! –

3

Jeremy Likness tiene un entrada en el blog con el título Coroutines for Asynchronous Sequential Workflows using Reactive Extensions (Rx) que podría interesarle. Aquí está la pregunta que intenta responder:

El concepto es sencillo: muchas veces queremos que un conjunto asíncrono de operaciones se realice de forma secuencial. Quizás deba cargar una lista de un servicio, luego cargar el elemento seleccionado y luego activar una animación. Esto se puede hacer encadenando los eventos completados o anidando expresiones lambda, pero ¿hay una manera más limpia?

+0

Muy interesante. Eso definitivamente puede ser útil. Veré qué otras respuestas surgen, especialmente las que usan la Biblioteca de tareas paralelas. – Jason

Cuestiones relacionadas