2011-08-07 30 views
9

Ayer comencé a jugar con la biblioteca asíncrona CTP de Microsoft, y en ninguna parte no pude encontrar la implementación adecuada de la tarea. Yo sé que debe tener aplicación como esto ?:Cómo hacer que la tarea sea deseable

public struct SampleAwaiter<T> 
{ 
    private readonly Task<T> task; 
    public SampleAwaiter(Task<T> task) { this.task = task; } 
    public bool IsCompleted { get { return task.IsCompleted; } } 
    public void OnCompleted(Action continuation) { TaskEx.Run(continuation); } 
    public T GetResult() { return task.Result; } 
} 

Pero ¿cómo iba a aplicar ahora una tarea que, digamos, esperar 5 segundos, y el retorno de alguna cadena, por ejemplo "Hello World"?

Una forma es utilizar directamente tareas de este modo:

Task<string> task = TaskEx.Run(
      () => 
       { 
        Thread.Sleep(5000); 
        return "Hello World"; 
       }); 

     string str = await task; 

Pero, ¿cómo iba a hacer eso con la implementación awaitable? ¿O simplemente lo malentendí todo?

Gracias por cualquier información/ayuda :)

+1

async/await se aplica a métodos, no a Tareas. –

+3

@Henk 'await' se aplica a las expresiones que tienen una implementación' GetAwaiter() 'adecuada, que' Tarea' hace - así que 'await' se aplica a' Tarea' (en cierto sentido) –

+0

En este caso, puede hacer ' espera TaskEx.Delay (5000); ', pero tendrás que estudiar más para tu caso general.La palabra clave 'await' busca' GetAwaiter', y básicamente cualquier cosa que proporcione un 'GetAwaiter' adecuado se puede usar con la espera. Jon Skeet tiene una gran serie en su blog que espera con gran detalle. –

Respuesta

16

La clave aquí es que proporciona AsyncCtpThreadingExtensions.GetAwaiter esos métodos a través de un método de extensión. Dado que la implementación asincrónica está basada en patrón (como LINQ), en lugar de estar vinculada a una interfaz específica, puede venir de todas partes (es TaskAwaiter en este caso).

Su código tal como está escrito es a la espera. Por ejemplo:

static void Main() 
{ 
    Test(); 
    Console.ReadLine(); // so the exe doesn't burninate 
} 
static async void Test() { 
    Task<string> task = TaskEx.Run(
      () => 
      { 
       Thread.Sleep(5000); 
       return "Hello World"; 
      }); 
    string str = await task; 
    Console.WriteLine(str); 
} 

Esto imprime Hello World después de 5 segundos.

+0

Entonces, ¿cómo se implementaría una clase con la implementación de GetAwaiter? – DavorinP

+1

@Pajci desde cero? Publiqué en este [aquí] (http://marcgravell.blogspot.com/2011/04/musings-on-async.html) y dejé un ejemplo en la fuente de BookSleeve, pero [pronto] concluí (http://marcgravell.blogspot.com/2011/04/completion-tasks-easy-way.html) que es preferible utilizar TaskCompletionSource - *** y *** preexistentes más rápido que mi versión. –

+0

Votación hacia arriba para el uso de la palabra 'burninate' – brandonstrong

1

Terminé con este código de muestra ... ¿es esta la implementación correcta del patrón de espera?

namespace CTP_Testing 
{ 
using System; 
using System.Linq; 
using System.Net; 
using System.Threading; 
using System.Threading.Tasks; 

public class CustomAsync 
{ 
    public static CustomAwaitable GetSiteHeadersAsync(string url) 
    { 
     return new CustomAwaitable(url); 
    } 
} 

public class CustomAwaitable 
{ 
    private readonly Task<string> task; 
    private readonly SynchronizationContext ctx; 

    public CustomAwaitable(string url) 
    { 
     ctx = SynchronizationContext.Current; 
     this.task = Task.Factory.StartNew(
      () => 
       { 
        var req = (HttpWebRequest)WebRequest.Create(url); 
        req.Method = "HEAD"; 
        var resp = (HttpWebResponse)req.GetResponse(); 
        return this.FormatHeaders(resp.Headers); 
       }); 
    } 
    public CustomAwaitable GetAwaiter() { return this; } 
    public bool IsCompleted { get { return task.IsCompleted; } } 
    public void OnCompleted(Action continuation) 
    { 
     task.ContinueWith(_ => ctx.Post(delegate { continuation(); }, null)); 
    } 
    public string GetResult() { return task.Result; } 

    private string FormatHeaders(WebHeaderCollection headers) 
    { 
     var headerString = headers.Keys.Cast<string>().Select(
      item => string.Format("{0}: {1}", item, headers[item])); 

     return string.Join(Environment.NewLine, headerString.ToArray()); 
    } 
} 

}

+0

Es MUCHO más simple que esto usar Async CTP para hacer una espera asíncrona de la respuesta web. Sugerencia: use 'Task.Factory.FromAsync', o busque los métodos con los nombres' xxxTaskAsync'. –

+0

¿Pero esta es la implementación correcta del patrón de espera? – DavorinP

+0

Creo que su awaiter necesita implementar BeginAwait y EndAwait. Deberías leer la serie de blogs de Jon Skeet sobre los espectadores personalizados ya que mencionó una gran cantidad de gotcha cuando los escribía. –

1

Adición un año más tarde

Después de usar asíncrono esperan desde hace más de un año, sé que algunas cosas sobre asíncrono que escribí en mi primera respuesta no es correcta, aunque el código en la respuesta sigue siendo correcto. Hera son dos enlaces que me ayudaron enormemente a entender cómo funciona async-await.

This interview Eric Lippert shows an excellent analogy for async-await. Busque en algún lugar en el medio para async-await.

In this article, the ever so helpful Eric Lippert shows some good practices for async-await

Respuesta original

OK, aquí es un ejemplo completo que me ayudó durante el proceso de aprendizaje.

Supongamos que tiene una calculadora lenta y desea usarla al presionar un botón. Mientras tanto, quieres que tu UI siga siendo receptiva e incluso puede hacer otras cosas. Cuando la calculadora finaliza, desea mostrar el resultado.

Y, por supuesto, utilice async/await para esto y ninguno de los métodos anteriores, como establecer indicadores de eventos y esperar a que se establezcan estos eventos.

Aquí es el lento calculadora:

private int SlowAdd(int a, int b) 
{ 
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5)); 
    return a+b; 
} 

Si desea utilizar esta forma asíncrona durante el uso asíncrono espera de usted tiene que utilizar Task.Run (...) para iniciarlo de forma asíncrona. El valor de retorno de Task.Run es una tarea awaitable:

  • Task si el valor de retorno de la función se ejecuta es nula
  • Task<TResult> si el valor de retorno de la función se ejecuta es TResult

Usted solo puede iniciar la Tarea, hacer otra cosa, y siempre que necesite el resultado de la Tarea, tipee en espera. Hay un inconveniente:

Si desea 'esperan' que su función tiene que ser asíncrono y volver Task en lugar de void o Task<TResult> en lugar de TResult.

Aquí está el código que ejecuta la calculadora lenta. Es una práctica común terminar el identificador de una función asíncrona con asincrónico.

private async Task<int> SlowAddAsync(int a, int b) 
{ 
    var myTask = Task.Run (() => SlowAdd(a, b)); 
    // if desired do other things while the slow calculator is working 
    // whenever you have nothing to do anymore and need the answer use await 
    int result = await myTask; 
    return result; 
} 

observación al margen: Algunas personas prefieren por encima de Task.Factory.StartNew Start.Run. Vea lo que dice acerca de esta MSDN:

MSDN: Task.Run versus Task.Factory.StartNew

El SlowAdd se inicia como una función asincrónica, y el hilo continúa. Una vez que necesita la respuesta, espera la Tarea. El valor de retorno es TResult, que en este caso es un int.

Si no tienes nada importante que hacer el código se vería así:

private async Task`<int`> SlowAddAsync(int a, int b) 
{ 
    return await Task.Run (() => SlowAdd(a, b)); 
} 

Tenga en cuenta que SlowAddAsync se declara una función asíncrona, por lo que todos los que usan esta función asíncrona también debe ser asíncrono y volver de tareas o tareas <TResult>:

private async Task UpdateForm() 
{ 
    int x = this.textBox1.Text; 
    int y = this.textBox2.Text; 
    int sum = await this.SlowAddAsync(x, y); 
    this.label1.Text = sum.ToString(); 
} 

lo bueno de asíncrono/espera es que usted no tiene que jugar con ContinueWith que esperar hasta que haya finalizado la tarea anterior. Simplemente use await, y sabrá que la tarea ha finalizado y que tiene el valor de retorno. La declaración después de aguardar es lo que normalmente harías en ContinueWith.

Por cierto, su Task.Run no tiene que llamar a una función, también puede poner un bloque de instrucciones en ella:

int sum = await Task.Run(() => { 
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5)); 
    return a+b}); 

Sin embargo lo bueno de una función separada es que le das aquellos que no necesitan/quieren/entienden asincron, la posibilidad de usar la función sin async/await.

Recuerde:

Cada función que utiliza Await debe ser asíncrono

Cada función debe devolver asíncrono de tareas o tareas <Tresult>

"Pero mi controlador de eventos no puede devolver una ¡Tarea!"

private void OnButton1_Clicked(object sender, ...){...} 

Tienes razón, por lo tanto, esa es la única excepción:

controladores de eventos asíncrono pueden volver vacío

Así que cuando se hace clic en el botón, el controlador de eventos asíncrono mantendría la interfaz de usuario sensible:

private async void OnButton1_Clicked(object sender, ...) 
{ 
    await this.UpdateForm(); 
} 

Sin embargo, usted todavía tiene que declarar t El controlador de eventos asincrónico

Muchas funciones .NET tienen versiones asíncronas que devuelven una Tarea o Tarea <TResult>.

Hay funciones asíncronas para - Acceso a Internet - Corriente de lectura y escritura - Base de datos de acceso - etc.

Para usarlos usted no tiene que llamar Task.Run, que ya regresan Tarea y Tarea <TResult> simplemente llámalas, continúa haciendo tus propias cosas y cuando necesites la respuesta espera por la Tarea y utiliza el TResult.

iniciar varias tareas y esperar a que terminen Si se inicia varias tareas y quiere esperar a que todos ellos para terminar, utilice Task.WhenAll (...) NO Task.Wait

Task.Wait devuelve un vacío. Task.WhenTodos devuelve una tarea, por lo que puede esperar por ella.

Una vez que se finaliza una tarea, el valor de retorno ya es el retorno de la espera, pero si se espera la Tarea.CuandoTodo (nueva Tarea [] {TareaA, TareaB, TareaC}); usted tiene que utilizar la tarea <TResult> .Result propiedad a conocer el resultado de una tarea:

int a = TaskA.Result; 

Si una de las tareas se produce una excepción, que se envuelve como InnerExceptions en un AggregateException. Entonces, si aguarda la Tarea. Cuando todo, prepárese para atrapar la Excepción de agregación y compruebe lasExcepciones internas para ver todas las excepciones lanzadas por las tareas que inició. Use la función AggregateException.Flatten para acceder a las excepciones más fácilmente.

interesante leer sobre la cancelación:

MSDN about Cancellation in managed threads

Por último: Se utiliza Thread.Sleep (...). La versión asíncrona es Task.Delay (TimeSpan):

private async Task`<int`> MySlowAdd(int a, int b) 
{ 
    await Task.Delay(TimeSpan.FromSeconds(5)); 
    return a+b; 
} 

Si utiliza esta función mantiene su programa de respuesta.

Cuestiones relacionadas