c# - Cómo hacer que la tarea sea fácil de usar
async-ctp async-await (3)
La clave aquí es AsyncCtpThreadingExtensions.GetAwaiter
que proporciona esos métodos a través de un método de extensión. Como la implementación asincrónica está basada en patrones (como LINQ), en lugar de vincularse a una interfaz específica, puede venir de todas partes (en este caso, es TaskAwaiter
).
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.
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 agitada. Sé que debe tener una implementación como esta ?:
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 implementaría ahora una tarea que, digamos, esperaría 5 segundos y devolvería una cadena, por ejemplo, "Hola mundo"?
Una forma es usar Tarea directamente así:
Task<string> task = TaskEx.Run(
() =>
{
Thread.Sleep(5000);
return "Hello World";
});
string str = await task;
¿Pero cómo haría eso con la implementación a la espera? ¿O simplemente lo malentendí todo?
Gracias por cualquier información / ayuda :)
Terminé con este código de muestra ... ¿es esta la implementación adecuada 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());
}
}
}
Además, un año después
Después de usar async-await por más de un año, sé que algunas cosas sobre async que escribí en mi respuesta original no son correctas, 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.
Esta entrevista Eric Lippert muestra una excelente analogía para async-await . Busque en algún lugar en el medio para async-await.
En este artículo, el siempre tan útil Eric Lippert muestra algunas buenas prácticas para async-await
Respuesta original
OK, aquí hay 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, quiere que su UI se mantenga receptiva, y tal vez incluso haga otras cosas. Cuando la calculadora finaliza, desea mostrar el resultado.
Y, por supuesto, use 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í está la calculadora lenta:
private int SlowAdd(int a, int b)
{
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
return a+b;
}
Si desea utilizar esto de forma asincrónica mientras usa async-await, debe usar Task.Run (...) para iniciarlo de forma asíncrona. El valor de retorno de Task.Run
es una tarea aguardable:
-
Task
si el valor de retorno de la función que ejecuta es nulo -
Task<TResult>
si el valor de retorno de la función que ejecuta esTResult
Puede simplemente comenzar la Tarea, hacer otra cosa, y cada vez que necesite el resultado de la Tarea, escriba esperar. Hay un inconveniente:
Si desea ''aguardar'', su función debe ser asincrónica y devolver la Task
lugar de void
o la Task<TResult>
lugar de TResult
.
Aquí está el código que ejecuta la calculadora lenta. Es una práctica común finalizar 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;
}
Comentario al margen: algunas personas prefieren Task.Factory.StartNew por encima de Start.Run. Vea lo que MSDN dice sobre esto:
SlowAdd se inicia como una función asíncrona y su 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 tiene nada significativo 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 utilicen esta función asíncrona también deben ser asincrónicos y regresar a Tarea o Tarea <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 async / await es que no tienes que jugar con ContinueWith para esperar hasta que la tarea anterior haya finalizado. 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 a aquellos que no necesitan / quieren / entienden asincronización, la posibilidad de usar la función sin async / await.
Recuerda:
Cada función que usa await debe ser asincrónica
Cada función asíncrona debe devolver Tarea o Tarea
<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:
los controladores de eventos asíncronos pueden volverse vacíos
Por lo tanto, al hacer clic en el botón, el controlador de eventos asíncrono mantendría la IU sensible:
private async void OnButton1_Clicked(object sender, ...)
{
await this.UpdateForm();
}
Sin embargo, todavía tiene que declarar async el controlador de eventos
Muchas funciones .NET tienen versiones asíncronas que devuelven una Tarea o Tarea
<TResult
>.
Hay funciones asíncronas para: acceso a Internet, lectura y escritura de flujo, acceso a la base de datos, etc.
Para usarlos, no es necesario llamar a Task.Run, ya devuelven Task and Task <TResult
> simplemente los llama, continúa haciendo sus cosas y cuando necesita la respuesta, aguarde la tarea y use TResult.
Inicie varias tareas y espere a que finalicen. Si inicia varias tareas y desea esperar a que todas ellas finalicen, utilice la Tarea. CuandoTodas (...) NO Tarea.Espere.
Tarea. Espera devuelve un vacío. Task.WhenTodos devuelve una tarea, por lo que puede esperar por ella.
Una vez que finaliza una tarea, el valor de retorno ya es el retorno de la espera, pero si espera la Tarea.CuandoTodo (nueva Tarea [] {Tarea A, Tarea B, TareaC}); debe usar la propiedad Task <TResult
> .Result para conocer el resultado de una tarea:
int a = TaskA.Result;
Si una de las tareas arroja una excepción, se envuelve como InnerExceptions en una 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 sobre la cancelación en subprocesos administrados
Finalmente: usas 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 usa esta función, su programa sigue siendo receptivo.