El problema es que está utilizando la Taskclase no genérica , que no está destinada a producir un resultado. Entonces, cuando crea la Taskinstancia pasando un delegado asíncrono:
Task myTask = new Task(async () =>
... el delegado es tratado como async void. Un async voidno 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 Startexterno 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 Unwrapque combina las tareas externas e internas en una sola:
await myTask.Unwrap();
Este desenvolvimiento ocurre automáticamente cuando usa el Task.Runmétodo mucho más popular que crea tareas activas , por lo Unwrapque 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 myTaskvariable es de tipo Task<Task<string>>.
Nota: No apruebo el uso de Taskconstructores 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 Taskno 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.