El problema es que está utilizando la Task
clase no genérica , que no está destinada a producir un resultado. Entonces, cuando crea la Task
instancia pasando un delegado asíncrono:
Task myTask = new Task(async () =>
... el delegado es tratado como async void
. Un async void
no es un Task
, no se puede esperar, su excepción no se puede manejar, y es una fuente de miles de preguntas hechas por programadores frustrados aquí en StackOverflow y en otros lugares. La solución es usar la Task<TResult>
clase genérica , porque desea devolver un resultado, y el resultado es otro Task
. Entonces tienes que crear un Task<Task>
:
Task<Task> myTask = new Task<Task>(async () =>
Ahora, cuando lo Start
externo Task<Task>
se completará casi instantáneamente porque su trabajo es solo crear lo interno Task
. Entonces también tendrás que esperar lo interno Task
. Así es como se puede hacer:
myTask.Start();
Task myInnerTask = await myTask;
await myInnerTask;
Tienes dos alternativas. Si no necesita una referencia explícita al interior Task
, puede esperar Task<Task>
dos veces al exterior :
await await myTask;
... o puede usar el método de extensión incorporado Unwrap
que combina las tareas externas e internas en una sola:
await myTask.Unwrap();
Este desenvolvimiento ocurre automáticamente cuando usa el Task.Run
método mucho más popular que crea tareas activas , por lo Unwrap
que no se usa con mucha frecuencia en la actualidad.
En caso de que decida que su delegado asíncrono debe devolver un resultado, por ejemplo a string
, entonces debe declarar que la myTask
variable es de tipo Task<Task<string>>
.
Nota: No apruebo el uso de Task
constructores para crear tareas en frío. Como una práctica generalmente está mal vista, por razones que realmente no conozco, pero probablemente porque se usa tan raramente que tiene el potencial de atrapar a otros usuarios / mantenedores / revisores del código por sorpresa.
Consejo general: tenga cuidado cada vez que proporcione un delegado asíncrono como argumento para un método. Idealmente, este método debería esperar un Func<Task>
argumento (lo que significa que entiende a los delegados asíncronos), o al menos un Func<T>
argumento (lo que significa que al menos lo generado Task
no será ignorado). En el desafortunado caso de que este método acepte un Action
, su delegado será tratado como async void
. Esto rara vez es lo que quieres, si es que alguna vez.