c# - Async-await Task.Run vs HttpClient.GetAsync
asynchronous task-parallel-library (3)
Soy nuevo en la función asíncrona de c # 5. Estoy tratando de entender la diferencia entre estas dos implementaciones:
Implementación 1:
private void Start()
{
foreach(var url in urls)
{
ParseHtml(url);
}
}
private async void ParseHtml(string url)
{
var query = BuildQuery(url); //BuildQuery is some helper method
var html = await DownloadHtml(query);
//...
MyType parsedItem = ParseHtml(html);
SaveTypeToDB(parsedItem);
}
private async Task<string> DownloadHtml(string query)
{
using (var client = new HttpClient())
try
{
var response = await client.GetAsync(query);
return (await response.Content.ReadAsAsync<string>());
}
catch (Exception ex)
{
Logger.Error(msg, ex);
return null;
}
}
Implementación 2:
private void DoLoop()
{
foreach(var url in urls)
{
Start(url);
}
}
private async void Start(url)
{
await Task.Run( () => ParseHtml(url)) ;
}
private void ParseHtml(string url)
{
var query = BuildQuery(url); //BuildQuery is some helper method
var html = DownloadHtml(query);
//...
MyType parsedItem = ParseHtml(html);
SaveTypeToDB(parsedItem);
}
private string DownloadHtml(string query)
{
using (var client = new WebClient())
{
try
{
return client.DownloadString(query);
}
catch (Exception ex)
{
Logger.Error(msg, ex);
return null;
}
}
}
Prefiero usar la segunda implementación, ya que requerirá menos firmas "asincrónicas" en los métodos en mi código. Estoy tratando de entender cuál es el beneficio de usar la clase HttpClient versus usar una nueva Tarea y esperarla en su lugar?
¿Hay alguna diferencia entre las dos implementaciones?
Prefiero usar la segunda implementación, ya que requerirá menos firmas "asincrónicas" en los métodos en mi código.
Eso suena como una justificación muy extraña. Tratas de ejecutar fundamentalmente "algo asíncronamente", así que ¿por qué no dejarlo en claro?
¿Hay alguna diferencia entre las dos implementaciones?
Absolutamente. La segunda implementación atará un hilo mientras WebClient.DownloadString
bloquea, esperando que se complete la solicitud. La primera versión no tiene ningún hilo bloqueado: se basa en una continuación que se activará cuando finalice la solicitud.
Además, considere su llamada Logger.Error
. En la versión asíncrona, eso aún se ejecutará en el contexto del código de llamada original. Entonces, si esto está en, por ejemplo, una interfaz de usuario de Windows Forms, aún estará en el hilo de la interfaz de usuario, y puede acceder a los elementos de la interfaz de usuario, etc. En la segunda versión, se ejecutará en un hilo del grupo de subprocesos y se necesitaría volver a la sincronización de la interfaz de usuario para actualizar la interfaz de usuario.
Tenga en cuenta que su método de async void
casi seguro no debería ser un async void
. Solo debe hacer que un método async
void
por el simple hecho de cumplir con las firmas del controlador de eventos. En todos los demás casos, vuelva Task
: de ese modo, la persona que llama puede ver cuándo ha finalizado su tarea, manejar excepciones, etc.
También tenga en cuenta que no necesita utilizar HttpClient
para la asincronía. En su lugar, podría usar WebClient.DownloadStringTaskAsync
, por lo que su método final podría convertirse en:
private async Task<string> DownloadHtml(string query)
{
using (var client = new WebClient())
{
try
{
return await client.DownloadStringTaskAsync(query);
}
catch (Exception ex)
{
Logger.Error(msg, ex);
return null;
}
}
}
Para las aplicaciones de servidor, async
consiste en minimizar el número de subprocesos bloqueados que tiene: aumentar la eficiencia del grupo de subprocesos y quizás permitir que su programa se adapte a más usuarios.
Para las aplicaciones cliente donde es poco probable que tenga que preocuparse por el conteo de subprocesos, async
proporciona una manera relativamente fácil de mantener su UI funcionando de forma fluida cuando realiza E / S.
Es muy diferente de Task.Run
debajo del capó.
Solo se beneficiará del procesamiento asincrónico si la secuencia de llamada tiene algo significativo que hacer, como mantener la IU en buen estado. Si su cadena de llamada solo inicia una tarea y no hace más que esperar hasta que la tarea finalice, su proceso se ejecutará más lentamente.
Sus segundas implementaciones inician una tarea, pero usted espera que termine sin hacer nada más. De esta forma no te beneficiarás.
No describe su entorno, pero si tiene una IU que debe seguir siendo receptiva, entonces la implementación del método 1 está bien, excepto que su Inicio () no se declara como sincronización y no espera:
private async Task StartAsync()
{
foreach (var url in urls)
{
await ParseHtml(url)
}
}
Puede llamarlo desde un controlador de eventos de la siguiente manera:
private async void OnButton1_clicked(object sender, ...)
{
await StartAsync();
}
Nota: el archivo ParseHtml está precedido por esperar. El siguiente html será analizado después de que el análisis previo haya finalizado. Sin embargo, como el análisis es asincrónico, el hilo de llamada (¿el hilo de la interfaz de usuario?) Podrá hacer otras cosas, como responder a la entrada del usuario.
Sin embargo, si su función parseHTML puede ejecutarse simultáneamente, sería preferible el siguiente código, y probablemente más rápido:
private async Task StartAsync()
{
var tasks = new List<Task>()
foreach (var url in urls)
{
tasks.Add(ParseHtml(url));
}
// now you have a sequence of Tasks scheduled to be run, possibly simultaneously.
// you can do some other processing here
// once you need to be certain that all tasks are finished await Task.WhenAll(...)
await Task.WhenAll(tasks);
// Task.WhenAls(...) returns a Task, hence you can await for it
// the return of the await is a void
}
- Si llama a una función que devuelve una Tarea, puede continuar haciendo otras cosas mientras la tarea se está ejecutando o esperar que la tarea termine y utilice los resultados de la tarea.
- si espera, su código se detiene, pero las personas que llaman continúan procesando hasta que esperan que su tarea se complete
- solo puede esperar en su procedimiento si su procedimiento es asincrónico.
- las funciones asincrónicas solo pueden ser llamadas por otras funciones asíncronas, o llamando a Task.Run (() => ...) o si se prefiere: Task.Factory.StartNew (() => ...)
- En lugar de vacío, una función asíncrona regresa a Tarea
- En lugar de TResult, una función asíncrona devuelve Task
<TResult
> - La única excepción es el controlador de eventos: declararlo asíncrono y devolver vacío.
- Si necesita que la tarea termine, solo espere la tarea.
- El regreso de la espera es el resultado.