C# Async ops

El nucleo de la programacion asincrona son los objetos Task y Task<T>. Ambos son compatibles con las palabras clave async y await.

Primero hay que identificar si el codigo se usa para trabajos enlazados a I/O o si estos son CPU intensivos.

  • Si el codigo espera algo como una BBDD o una response de un servidor, es codigo I/O. En este caso hay que usar async y await
  • Si el codigo realiza un calculo costoso, es CPU intensivo. Use await para esperar una operacion que se ha comenzado en background con Task.run

Async / Await (Operaciones I/O)

La palabra clave importante aqui es await. Lo que hace es suspender la ejecucion del metodo actual y devolver el control hasta que está lista para seguir.

public async Task Main()
{
	string contenido = await LeerPaginaWebAsync("http://example.com");
}

private async Task<string> LeerPaginaWebAsync(string url)
{
	using (HttpClient client = new HttpClient())
	{
		return await client.GetStringAsync(url);
	}
}

Task.Run (Codigo CPU intensivo)

usando Task.Run() fuerzas el invocar un hilo que no sea el hilo pincipal para no bloquearlo. Se usa para procesos intensivos de CPU y bloqueantes.
No usar Task.Run en la implementación de un método. Usar Task.Run para llamar al método.

(!) atención (!) async code it not the same as parallel code.

  • w. async code you are trying to make your threads do as little work as possible, this will keep your app responsive, capable to serve many requests at once and scale well
  • w. parallel code you do the opposite. you use and keep a hold on a thread to do CPU-intensive heavy calculations
public async Task Main()
{
	string contenido = await Task.Run(() => OperacionCpuIntensiva("http://example.com"));
}

private async Task<string> OperacionCpuIntensiva(string url)
{
	// do something really CPU intensive
}

Task.WhenAll (Esperando a que se completen varias tareas concurrentes)

Para situaciones en las que se necesiten recuperar fragmentos de datos al mismo tiempo.

public Task Main()
{
	var urls = new List<string>
	{
		"http://example.com", 
		"http://example.org", 
		"http://example.net"
	}

	// se inicia la lectura  de todas las paginas web de manera concurrente
	var tasksDeLectura = urls.Select(LeerPaginaWebAsync).ToList();

	// esperar a que todas las tareas de lectura se completen
	string[] contenidos = await Task.WhenAll(tasksDeLectura);

	foreach (var contenido in contenidos) 
	{
		// hacer algo con las paginas web
	}

}

public async Task<string> LeerPaginaWebAsync(string url)
{
	using (var client = new HttpClient())
	{
		return await client.GetStringAsync(url);
	}
}

Task vs ValueTask

ValueTask is a special implementation of a Task, optimized to reduce Task’s overhead in certain cases. It’s main difference is in the way ValueTask handles results.

Task

A task always creates an additional Task instance, even if the Task can be completed synchronously. This can lead to overhead in some cases, specially if you create many small, short-lived tasks.

ValueTask

Is designed to minimize overhead by returning the result directly as a value if the task can be completed synchronously. This is particularly useful when:

  • The task is likely to finish synchronously
  • You create many small tasks that finish quickly
  • For performance-intensive applications

Reference(s)

https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/async-scenarios
https://blog.stephencleary.com/2013/10/taskrun-etiquette-and-proper-usage.html
https://dev.to/ben-witt/task-vs-valuetask