O problema é que você está usando a Task
classe não genérica , que não pretende produzir um resultado. Portanto, quando você cria a Task
instância passando um delegado assíncrono:
Task myTask = new Task(async () =>
... o delegado é tratado como async void
. Um async void
não é um Task
, não pode ser esperado, sua exceção não pode ser tratada e é uma fonte de milhares de perguntas feitas por programadores frustrados aqui no StackOverflow e em outros lugares. A solução é usar a Task<TResult>
classe genérica , porque você deseja retornar um resultado, e o resultado é outro Task
. Então você tem que criar um Task<Task>
:
Task<Task> myTask = new Task<Task>(async () =>
Agora, quando você for Start
externo Task<Task>
, será concluído quase instantaneamente, porque seu trabalho é apenas criar o interior Task
. Você terá que aguardar o interior Task
também. É assim que isso pode ser feito:
myTask.Start();
Task myInnerTask = await myTask;
await myInnerTask;
Você tem duas alternativas. Se você não precisar de uma referência explícita ao interno Task
, poderá aguardar o externo Task<Task>
duas vezes:
await await myTask;
... ou você pode usar o método de extensão Unwrap
interno que combina as tarefas externa e interna em uma:
await myTask.Unwrap();
Esse desempacotamento acontece automaticamente quando você usa o Task.Run
método muito mais popular que cria tarefas quentes, portanto, ele Unwrap
não é usado com muita frequência atualmente.
Caso você decida que seu representante assíncrono deve retornar um resultado, por exemplo string
, a , declare que a myTask
variável é do tipo Task<Task<string>>
.
Nota: Eu não apoio o uso de Task
construtores para criar tarefas frias. Como uma prática geralmente é desaprovada, por motivos que eu realmente não conheço, mas provavelmente porque é usada tão raramente que tem o potencial de capturar outros usuários / mantenedores / revisores desconhecidos do código de surpresa.
Conselho geral: tenha cuidado sempre que estiver fornecendo um delegado assíncrono como argumento para um método. Idealmente, esse método deve esperar um Func<Task>
argumento (o que significa que compreende delegados assíncronos) ou pelo menos um Func<T>
argumento (o que significa que pelo menos o gerado Task
não será ignorado). No infeliz caso em que esse método aceite um Action
, seu delegado será tratado como async void
. Isso raramente é o que você deseja, se é que alguma vez.