c# - ¿Por qué exactamente el vacío asíncrono es malo?
async-await (2)
Bueno, repasando las razones en el artículo msdn.microsoft.com/en-us/magazine/jj991977.aspx :
-
Los métodos de vacío asíncrono tienen diferentes semánticas de manejo de errores.
Las excepciones que se escapen de
PrimeCustomTask
serán muy difíciles de manejar. -
Los métodos de vacío asíncrono tienen una semántica de composición diferente.
Este es un argumento centrado en la mantenibilidad y reutilización del código.
Esencialmente, la lógica en
PrimeCustomTask
está ahí y eso es todo: no se puede componer en un métodoasync
nivel superior. -
Los métodos de vacío asíncrono son difíciles de probar.
Siguiendo naturalmente los primeros dos puntos, es muy difícil escribir una prueba unitaria que cubra
PrimeCustomTask
(o cualquier cosa que lo llame).
También es importante tener en cuenta que la
async Task
es el enfoque natural.
De los
varios lenguajes que han adoptado
async
/
await
, C # / VB son los únicos AFAIK que admiten
async void
.
F # no, Python no, JavaScript y TypeScript no.
async void
no es natural desde la perspectiva del diseño del lenguaje.
La razón por la que se agregó el
async void
a C # / VB fue para habilitar controladores de eventos asíncronos.
Si cambia su código para usar controladores de eventos anulados
async void
:
protected override async void OnLoad(EventArgs e)
{
if (CustomTask == null)
await PrimeCustomTask();
}
private async Task PrimeCustomTask()
Luego, las desventajas de
async void
se limitan a su controlador de eventos.
En particular, las excepciones de
PrimeCustomTask
se propagan naturalmente a sus llamadores (asíncronos) (
OnLoad
),
PrimeCustomTask
puede componerse (llamado naturalmente desde otros métodos asincrónicos), y
PrimeCustomTask
es mucho más fácil de incluir en una prueba unitaria.
Entonces entiendo por qué regresar vacío de async normalmente no tendría sentido, pero me he encontrado con una situación en la que creo que sería perfectamente válido. Considere el siguiente ejemplo artificial:
protected override void OnLoad(EventArgs e)
{
if (CustomTask == null)
// Do not await anything, let OnLoad return.
PrimeCustomTask();
}
private TaskCompletionSource<int> CustomTask;
// I DO NOT care about the return value from this. So why is void bad?
private async void PrimeCustomTask()
{
CustomTask = new TaskCompletionSource<int>();
int result = 0;
try
{
// Wait for button click to set the value, but do not block the UI.
result = await CustomTask.Task;
}
catch
{
// Handle exceptions
}
CustomTask = null;
// Show the value
MessageBox.Show(result.ToString());
}
private void button1_Click(object sender, EventArgs e)
{
if (CustomTask != null)
CustomTask.SetResult(500);
}
Me doy cuenta de que este es un ejemplo inusual, pero traté de hacerlo simple y más generalizado. ¿Podría alguien explicarme por qué este es un código horrible y también cómo podría modificarlo para seguir las convenciones correctamente?
Gracias por cualquier ayuda.
El uso de void async solo se ve generalmente como "malo" porque:
- No puedes esperar a que se complete (como ya se mencionó en esta publicación)
- Cualquier excepción no controlada terminará su proceso (¡ay!)
Hay muchos casos (como el tuyo) donde usarlo está bien. Solo tenga cuidado al usarlo.