Il problema è che stai usando la Task
classe non generica , che non ha lo scopo di produrre un risultato. Quindi quando si crea l' Task
istanza passando un delegato asincrono:
Task myTask = new Task(async () =>
... il delegato viene trattato come async void
. Un async void
non è una Task
, non può essere atteso, la sua eccezione non può essere gestito, ed è una fonte di migliaia di domande fatte dai programmatori frustrati qui in StackOverflow e altrove. La soluzione è utilizzare la Task<TResult>
classe generica , perché si desidera restituire un risultato e il risultato è un altro Task
. Quindi devi creare un Task<Task>
:
Task<Task> myTask = new Task<Task>(async () =>
Ora quando sei Start
esterno Task<Task>
sarà completato quasi istantaneamente perché il suo compito è solo quello di creare l'interno Task
. Dovrai quindi aspettare anche l'interno Task
. Ecco come si può fare:
myTask.Start();
Task myInnerTask = await myTask;
await myInnerTask;
Hai due alternative. Se non hai bisogno di un riferimento esplicito all'interiore Task
, puoi semplicemente attendere l'esterno Task<Task>
due volte:
await await myTask;
... oppure puoi utilizzare il metodo di estensione incorporato Unwrap
che combina le attività esterne e interne in una:
await myTask.Unwrap();
Questo scartamento si verifica automaticamente quando si utilizza il Task.Run
metodo molto più popolare che crea attività a caldo, quindi al Unwrap
giorno d'oggi non viene utilizzato molto spesso.
Nel caso in cui decidi che il tuo delegato asincrono deve restituire un risultato, ad esempio a string
, devi dichiarare che la myTask
variabile è di tipo Task<Task<string>>
.
Nota: non approvo l'uso di Task
costruttori per la creazione di attività a freddo. Dato che una pratica è generalmente disapprovata, per ragioni che non conosco davvero, ma probabilmente perché è usata così raramente che ha il potenziale di sorprendere di sorpresa altri utenti / manutentori / revisori del codice.
Consiglio generale: fare attenzione ogni volta che si fornisce un delegato asincrono come argomento a un metodo. Questo metodo dovrebbe idealmente aspettarsi un Func<Task>
argomento (nel senso che comprende i delegati asincroni), o almeno un Func<T>
argomento (nel senso che almeno il generato Task
non verrà ignorato). Nel malaugurato caso in cui questo metodo accetti un Action
, il tuo delegato verrà trattato come async void
. Questo è raramente quello che vuoi, se mai.